Bring back remmina server features with new additions

This commit is contained in:
bhatman1441 2024-01-30 18:56:26 +05:30
parent b2686ae53e
commit 8920157bcc
34 changed files with 3840 additions and 480 deletions

View File

@ -498,6 +498,33 @@ if(WITH_TRACE_CALLS)
add_definitions(-DWITH_TRACE_CALLS)
endif()
option(WITH_NEWS "Enable online news check" ON)
if(WITH_NEWS)
message(STATUS "Enabling news check connecting to remmina.org")
add_definitions(-DWITH_NEWS)
else()
message(STATUS "Disabling news check connecting to remmina.org")
add_definitions(-DDISABLE_NEWS)
endif()
option(WITH_STATS "Enable stats" ON)
if(WITH_STATS)
message(STATUS "Enabling stats")
add_definitions(-DWITH_STATS)
else()
message(STATUS "Disabling stats")
add_definitions(-DDISABLE_STATS)
endif()
option(WITH_TIP "Enable tip of the day" ON)
if(WITH_TIP)
message(STATUS "Enabling tip of the day")
add_definitions(-DWITH_TIP)
else()
message(STATUS "Disabling tip of the day")
add_definitions(-DDISABLE_TIP)
endif()
option(WITH_MANPAGES "Build with MANPAGES" ON)
if(WITH_MANPAGES)
message(STATUS "Enabling man pages.")

View File

@ -33,7 +33,6 @@
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
#pkg_check_modules(_WEBKIT2GTK webkit2gtk-4.0)
pkg_search_module(_WEBKIT2GTK webkit2gtk-4.1 webkit2gtk-4.0)
endif(PKG_CONFIG_FOUND)
@ -50,6 +49,7 @@ find_library(WEBKIT2GTK_LIB
${_WEBKIT2GTK_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(WEBKIT2GTK DEFAULT_MSG WEBKIT2GTK_LIB WEBKIT2GTK_INCLUDE_DIR)

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkDialog" id="RemminaBugReportDialog">
<property name="can-focus">False</property>
<property name="window-position">center-on-parent</property>
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="icon-name">org.remmina.Remmina</property>
<property name="type-hint">normal</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkBox" id="bug_report_box">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="bug_report_buttons">
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkCheckButton" id="bug_report_include_system_info_check_button">
<property name="label" translatable="yes">Include System Info</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="margin-left">15</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="bug_report_debug_data_check_button">
<property name="label" translatable="yes">Include Debug Data</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="margin-left">15</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="bug_report_submit_button">
<property name="label" translatable="yes">Submit</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="margin-right">15</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bug_report_fields_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid" id="bug_report_info_grid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="margin-top">5</property>
<property name="column-spacing">5</property>
<child>
<object class="GtkLabel" id="bug_report_name_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Name/Username:</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="bug_report_name_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="max-length">80</property>
<property name="activates-default">True</property>
<property name="placeholder-text" translatable="yes">Name</property>
<property name="input-purpose">name</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="bug_report_email_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">E-mail:</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="bug_report_email_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="max-length">80</property>
<property name="placeholder-text" translatable="yes">Email</property>
<property name="input-purpose">email</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="bug_report_title_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Bug Title</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="bug_report_title_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="max-length">400</property>
<property name="placeholder-text" translatable="yes">Bug Title</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="bug_report_description_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Bug Description</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="shadow-type">in</property>
<property name="propagate-natural-width">True</property>
<property name="propagate-natural-height">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkTextView" id="bug_report_description_textview">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="wrap-mode">word-char</property>
<property name="left-margin">5</property>
<property name="right-margin">5</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="bug_report_disclaimer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="label" translatable="yes">Clicking "Submit" will send this to Remmina's GitLab page and automatically create a new issue on the issue tracker.</property>
<property name="wrap">True</property>
<property name="max-width-chars">80</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">10</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bug_report_status_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="bug_report_submit_status_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-bottom">20</property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Remmina Report Bug</property>
<property name="show-close-button">True</property>
</object>
</child>
</object>
</interface>

100
data/ui/remmina_info.glade Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.20"/>
<!-- interface-license-type gplv2 -->
<!-- interface-name Remmina news widget -->
<!-- interface-description A widget that show the release notes and some quick settings -->
<!-- interface-copyright Antenore Gatta and Giovanni Panozzo -->
<!-- interface-authors Antenore Gatta and Giovanni Panozzo -->
<object class="GtkDialog" id="RemminaInfoDialog">
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="default-width">640</property>
<property name="default-height">480</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<property name="urgency-hint">True</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="remmina_info_button_close">
<property name="label" translatable="yes">Close</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="remmina_info_scrolled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<property name="min-content-height">380</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="remmina_info_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">18</property>
<property name="margin-right">18</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
<property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;The news failed to load&lt;/b&gt;&lt;/big&gt;
&lt;span&gt;
&lt;a href="https://gitlab.com/Remmina/Remmina/-/tags/" title="Remmina release notes"&gt;&lt;i&gt;Visit the website to read the release notes&lt;/i&gt;&lt;/a&gt;.
&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="wrap-mode">word-char</property>
<property name="selectable">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -331,6 +331,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem_bug_report">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">app.bug_report</property>
<property name="label" translatable="yes">Report Bug</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="menuitem_help_separator1">
<property name="visible">True</property>

View File

@ -1,272 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<!-- interface-license-type gplv2 -->
<!-- interface-name Remmina news widget -->
<!-- interface-description A widget that show the release notes and some quick settings -->
<!-- interface-copyright Antenore Gatta and Giovanni Panozzo -->
<!-- interface-authors Antenore Gatta and Giovanni Panozzo -->
<object class="GtkDialog" id="RemminaNewsDialog">
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="default_width">640</property>
<property name="default_height">480</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="urgency_hint">True</property>
<child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="rmnews_button_close">
<property name="label" translatable="yes">Close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="rmnews_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">380</property>
<property name="propagate_natural_height">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="rmnews_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">18</property>
<property name="margin_right">18</property>
<property name="margin_top">18</property>
<property name="margin_bottom">18</property>
<property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;The news are turned off&lt;/b&gt;&lt;/big&gt;
&lt;span&gt;
Turning on news means the program connects to a Remmina server to download the release notes.
&lt;/span&gt;
&lt;span&gt;
Version checking can only be activated at compile time.
&lt;/span&gt;
&lt;span&gt;
&lt;a href="https://gitlab.com/Remmina/Remmina/-/tags/" title="Remmina release notes"&gt;&lt;i&gt;Visit the website to read the release notes&lt;/i&gt;&lt;/a&gt;.
&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.5</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkLabel" id="rmnews_defaultcl_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Use as default remote desktop client</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="rmnews_defaultcl_button">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Allow Remmina to automatically open .rdp and .remmina files.</property>
<property name="halign">start</property>
<signal name="clicked" handler="rmnews_defaultcl_on_click" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="rmnews_allow_news_label">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes" comments="The star (*) is a reference to privacy consent">Fetch news from &lt;a href="https://remmina.org" title="Remmina news site"&gt;remmina.org&lt;/a&gt; (*)</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="rmnews_news_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Receives updated news from remmina.org</property>
<property name="halign">start</property>
<signal name="notify" handler="rmnews_news_switch_state_set_cb" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="privacy_disclaimer_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">* By turning on news you consent to fetching data from remmina.org</property>
<property name="justify">fill</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="width">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="label" translatable="yes">&lt;big&gt;Take part&lt;/big&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.5</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Pay attention to the quoting characters as they may break the pango layout. If in doubt and cannot test, copy and paste the symbols from the English string.">&lt;span&gt;
&lt;b&gt;You have our gratitude in choosing copylefted libre software, &lt;a href="https://remmina.org/donations/" title="Wheres the money, Lebowski? “blblblblblb”"&gt;donations also make us happy&lt;/a&gt;, and further help improve Remmina.&lt;/b&gt;
&lt;/span&gt;
</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="track_visited_links">False</property>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="label" translatable="yes">&lt;big&gt;Contribute&lt;/big&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -26,7 +26,7 @@
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<!-- n-columns=3 n-rows=15 -->
<!-- n-columns=3 n-rows=17 -->
<object class="GtkGrid" id="grid_options">
<property name="visible">True</property>
<property name="can-focus">False</property>
@ -389,7 +389,7 @@
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">11</property>
<property name="top-attach">14</property>
</packing>
</child>
<child>
@ -402,7 +402,7 @@
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">11</property>
<property name="top-attach">14</property>
<property name="width">2</property>
</packing>
</child>
@ -450,7 +450,7 @@
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">12</property>
<property name="top-attach">15</property>
</packing>
</child>
<child>
@ -464,7 +464,104 @@
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">15</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="remmina_info_disable_stats_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-left">18</property>
<property name="margin-right">6</property>
<property name="margin-start">18</property>
<property name="margin-end">6</property>
<property name="label" translatable="yes">Disable anonymous statistics</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">12</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_disable_stats">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="margin-right">18</property>
<property name="margin-end">18</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">12</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="remmina_info_disable_news_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-left">18</property>
<property name="margin-right">6</property>
<property name="margin-start">18</property>
<property name="margin-end">6</property>
<property name="label" translatable="yes">Disable news from &lt;a href="https://remmina.org" title="Remmina news site"&gt;remmina.org&lt;/a&gt;</property>
<property name="use-markup">True</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">11</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_disable_news">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="margin-right">18</property>
<property name="margin-end">18</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">11</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="remmina_info_disable_tip">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-left">18</property>
<property name="margin-right">6</property>
<property name="margin-start">18</property>
<property name="margin-end">6</property>
<property name="label" translatable="yes">Disable tip of the day</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">13</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="switch_disable_tip">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="margin-right">18</property>
<property name="margin-end">18</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">13</property>
<property name="width">2</property>
</packing>
</child>

View File

@ -478,6 +478,21 @@
}
]
},
{
"name": "curl",
"buildsystem": "autotools",
"cleanup": [],
"config-opts": [
"--with-openssl"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/curl/curl/releases/download/curl-7_81_0/curl-7.81.0.tar.xz",
"sha256": "a067b688d1645183febc31309ec1f3cdce9213d02136b6a6de3d50f69c95a7d3"
}
]
},
{
"name": "freerdp",
"buildsystem": "cmake-ninja",

View File

@ -469,6 +469,21 @@
}
]
},
{
"name": "curl",
"buildsystem": "autotools",
"cleanup": [],
"config-opts": [
"--with-openssl"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/curl/curl/releases/download/curl-7_81_0/curl-7.81.0.tar.xz",
"sha256": "a067b688d1645183febc31309ec1f3cdce9213d02136b6a6de3d50f69c95a7d3"
}
]
},
{
"name": "freerdp",
"buildsystem": "cmake-ninja",

View File

@ -57,10 +57,6 @@
* An null terminated array of commands that are executed after the initialization of the Python engine. Every entry
* represents a line of Python code.
*/
static const char
* python_init_commands[] = { "import sys", "sys.path.append('" REMMINA_RUNTIME_PLUGINDIR "')", NULL // Sentinel
};
static const gchar* python_wrapper_supported_extensions[] = {"py", NULL};
@ -200,11 +196,21 @@ G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
python_wrapper_module_init();
Py_InitializeEx(0);
for (const char** ptr = python_init_commands; *ptr; ++ptr)
gchar* plugin_dir;
plugin_dir = g_build_path("/", g_get_user_config_dir(), "remmina", "plugins", NULL);
gchar* python_command = g_strdup_printf("sys.path.append('%s')", plugin_dir);
char* python_init_commands[] = { "import sys", python_command, "sys.path.append('" REMMINA_RUNTIME_PLUGINDIR "')", NULL }; // Sentinel};
for (char** ptr = python_init_commands; *ptr; ++ptr)
{
PyRun_SimpleString(*ptr);
}
g_free(python_command);
g_free(plugin_dir);
python_wrapper_protocol_widget_init();
service->register_plugin((RemminaPlugin*)&remmina_python_wrapper);

View File

@ -178,9 +178,11 @@ parts:
- libjson-glib-dev
- libkf5wallet-dev
- libsodium-dev
- libcurl4-openssl-dev
- libspice-client-gtk-3.0-dev
- libspice-protocol-dev
- libssh-dev
- libssl3
- libvncserver-dev
- libvte-2.91-dev
- libwebkit2gtk-4.0-dev
@ -223,7 +225,7 @@ parts:
- -DSNAP_BUILD=on
# XXX: This is an hack to have a kind of bind-mount with absolute prefix.
- -DCMAKE_INSTALL_PREFIX=/snap/$SNAPCRAFT_PROJECT_NAME/current/usr
- -DCMAKE_INSTALL_PREFIX=/snap/$CRAFT_PROJECT_NAME/current/usr
organize:
snap/remmina/current/usr: usr

View File

@ -42,6 +42,8 @@ list(
"remmina_avahi.h"
"remmina.c"
"remmina.h"
"remmina_bug_report.c"
"remmina_bug_report.h"
"remmina_chat_window.c"
"remmina_chat_window.h"
"remmina_crypt.c"
@ -97,6 +99,8 @@ list(
"remmina_sftp_plugin.h"
"remmina_sodium.c"
"remmina_sodium.h"
"remmina_curl_connector.c"
"remmina_curl_connector.h"
"remmina_ssh.c"
"remmina_ssh.h"
"remmina_ssh_plugin.c"
@ -121,15 +125,17 @@ list(
"remmina_mpchange.h"
"remmina_scheduler.c"
"remmina_scheduler.h"
"remmina_stats.c"
"remmina_stats.h"
"remmina_info.c"
"remmina_info.h"
"resources.c")
set(RESOURCE_LIST
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_about.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_bug_report.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_key_chooser.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_main.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_mpc.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_info.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_passwd.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_preferences.glade
${CMAKE_CURRENT_SOURCE_DIR}/../data/ui/remmina_search.glade
@ -250,6 +256,14 @@ if(GTK3_FOUND)
endif()
endif()
find_required_package(CURL)
if(NOT CURL_FOUND)
message(FATAL_ERROR "libcurl not found")
else()
include_directories(${CURL_INCLUDE_DIRS})
target_link_libraries(remmina ${CURL_LIBRARIES})
endif()
find_required_package(PCRE2)
if(NOT PCRE2_FOUND)
message(FATAL_ERROR "libpcre2 library not found")
@ -264,3 +278,5 @@ install(
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/remmina
FILES_MATCHING
PATTERN "*.h")
add_definitions(-DG_LOG_DOMAIN="remmina")

View File

@ -61,6 +61,21 @@ typedef struct _RemminaPlugin {
const gchar * version;
} RemminaPlugin;
typedef struct _RemminaServerPluginResponse {
const gchar * name;
const gchar * version;
const gchar * file_name;
/*
* This is the signature received directly from the server. It should be base64 encoded.
*/
const guchar * signature;
/*
* This is the data received directly from the server. It should be
* first compressed with gzip and then base64 encoded.
*/
guchar * data;
} RemminaServerPluginResponse;
typedef struct _RemminaProtocolPlugin _RemminaProtocolPlugin;
typedef struct _RemminaProtocolPlugin {
RemminaPluginType type;

View File

@ -31,7 +31,7 @@
* files in the program, then also delete it here.
*
*/
#include <curl/curl.h>
#include <gdk/gdk.h>
#define G_LOG_USE_STRUCTURED
@ -53,6 +53,7 @@
#include "remmina_exec.h"
#include "remmina_file_manager.h"
#include "remmina_icon.h"
#include "remmina_log.h"
#include "remmina_main.h"
#include "remmina_masterthread_exec.h"
#include "remmina_plugin_manager.h"
@ -66,6 +67,7 @@
#include "remmina_ssh_plugin.h"
#include "remmina_widget_pool.h"
#include "remmina/remmina_trace_calls.h"
#include "remmina_info.h"
#ifdef HAVE_ERRNO_H
#include <errno.h>
@ -85,6 +87,9 @@ static int gcrypt_thread_initialized = 0;
#endif /* HAVE_LIBGCRYPT */
gboolean kioskmode;
gboolean imode;
gboolean disablenews;
gboolean disablestats;
gboolean disabletoolbar;
gboolean fullscreen;
gboolean extrahardening;
@ -129,6 +134,8 @@ static GOptionEntry remmina_options[] =
// TRANSLATORS: Shown in terminal. Do not use characters that may be not supported on a terminal
{ "set-option", 0, 0, G_OPTION_ARG_STRING_ARRAY, NULL, N_("Set one or more profile settings, to be used with --update-profile"), NULL },
{ "encrypt-password", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Encrypt a password"), NULL },
{ "disable-news", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Disable news"), NULL },
{ "disable-stats", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Disable stats"), NULL },
{ "disable-toolbar", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Disable toolbar"), NULL },
{ "enable-fullscreen", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Enable fullscreen"), NULL },
{ "enable-extra-hardening", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Enable extra hardening (disable closing confirmation, disable unsafe shortcut keys, hide tabs, hide search bar)"), NULL },
@ -166,6 +173,14 @@ static gint remmina_on_command_line(GApplication *app, GApplicationCommandLine *
#endif
opts = g_application_command_line_get_options_dict(cmdline);
if (g_variant_dict_lookup_value(opts, "disable-news", NULL)) {
disablenews = TRUE;
}
if (g_variant_dict_lookup_value(opts, "disable-stats", NULL)) {
disablestats = TRUE;
}
if (g_variant_dict_lookup_value(opts, "disable-toolbar", NULL)) {
disabletoolbar = TRUE;
}
@ -191,6 +206,7 @@ static gint remmina_on_command_line(GApplication *app, GApplicationCommandLine *
}
if (g_variant_dict_lookup_value(opts, "about", NULL)) {
imode = TRUE;
remmina_exec_command(REMMINA_COMMAND_ABOUT, NULL);
executed = TRUE;
}
@ -214,6 +230,7 @@ static gint remmina_on_command_line(GApplication *app, GApplicationCommandLine *
}
if (g_variant_dict_lookup(opts, "edit", "^aay", &files)) {
imode = TRUE;
if (files)
for (gint i = 0; files[i]; i++) {
g_debug ("Editing file: %s", files[i]);
@ -226,6 +243,7 @@ static gint remmina_on_command_line(GApplication *app, GApplicationCommandLine *
if (g_variant_dict_lookup_value(opts, "kiosk", NULL)) {
kioskmode = TRUE;
imode = TRUE;
remmina_exec_command(REMMINA_COMMAND_MAIN, NULL);
executed = TRUE;
}
@ -245,6 +263,7 @@ static gint remmina_on_command_line(GApplication *app, GApplicationCommandLine *
}
if (g_variant_dict_lookup(opts, "pref", "&s", &str)) {
imode = TRUE;
remmina_exec_command(REMMINA_COMMAND_PREF, str);
executed = TRUE;
}
@ -281,7 +300,6 @@ static void remmina_on_startup(GApplication *app)
remmina_sftp_plugin_register();
remmina_ssh_plugin_register();
remmina_icon_init();
g_set_application_name("Remmina");
gtk_window_set_default_icon_name(REMMINA_APP_ID);
@ -292,9 +310,19 @@ static void remmina_on_startup(GApplication *app)
gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(),
REMMINA_RUNTIME_DATADIR G_DIR_SEPARATOR_S "icons");
g_application_hold(app);
remmina_info_schedule();
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "w"); // Flush log file
g_free(log_filename);
if (log_file != NULL) {
fclose(log_file);
}
/* Check for secret plugin and service initialization and show console warnings if
* something is missing */
remmina_plugin_manager_get_available_plugins();
secret_plugin = remmina_plugin_manager_get_secret_plugin();
if (!secret_plugin)
g_print("Warning: Remmina is running without a secret plugin. Passwords will be saved in a less secure way.\n");
@ -363,7 +391,7 @@ int main(int argc, char *argv[])
*/
g_message(_("Remmina does not log all output statements. "
"Turn on more verbose output by using "
"\"G_MESSAGES_DEBUG=all\" as an environment variable.\n"
"\"G_MESSAGES_DEBUG=remmina\" as an environment variable.\n"
"More info available on the Remmina wiki at:\n"
"https://gitlab.com/Remmina/Remmina/-/wikis/Usage/Remmina-debugging"
));
@ -397,9 +425,9 @@ int main(int argc, char *argv[])
#endif /* !HAVE_LIBGCRYPT */
/* Initialize some Remmina parts needed also on a local instance for correct handle-local-options */
curl_global_init(CURL_GLOBAL_ALL);
remmina_pref_init();
remmina_file_manager_init();
remmina_plugin_manager_init();

View File

@ -38,4 +38,5 @@
G_BEGIN_DECLS
extern gboolean kioskmode;
extern gboolean imode;
G_END_DECLS

View File

@ -100,6 +100,12 @@ The filetype can be ".remmina" or one supported by a plugin capable of
*--display=DISPLAY*
X display to use
*--disable-news*
Disable news
*--disable-stats*
Disable stats
*--disable-toolbar*
Disable toolbar

410
src/remmina_bug_report.c Normal file
View File

@ -0,0 +1,410 @@
/*
* Remmina - The GTK+ Remote Desktop Client
* Copyright (C) 2009-2011 Vic Lee
* Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL.
* If you modify file(s) with this exception, you may extend this exception
* to your version of the file(s), but you are not obligated to do so.
* If you do not wish to do so, delete this exception statement from your
* version.
* If you delete this exception statement from all source
* files in the program, then also delete it here.
*
*/
#include <glib/gi18n.h>
#include <string.h>
#include "remmina_log.h"
#include "remmina_bug_report.h"
#include "remmina_public.h"
#include "remmina/remmina_trace_calls.h"
#include "remmina_info.h"
#include "remmina_curl_connector.h"
#include "remmina_utils.h"
#if !JSON_CHECK_VERSION(1, 2, 0)
#define json_node_unref(x) json_node_free(x)
#endif
#define BUG_REPORT_UPLOAD_URL "https://info.remmina.org/bug_report/bug_report"
static RemminaBugReportDialog *remmina_bug_report_dialog;
#define GET_OBJECT(object_name) gtk_builder_get_object(remmina_bug_report_dialog->builder, object_name)
#define TEMP_LOG_TEXT_FILE_NAME "remmina_shorter_logs.log"
#define COMPRESSED_LOG_FILE_NAME "remmina_log_file.gz"
#define LOG_RECENT_WINDOW 4000
#define MAX_DESCRIPTION_LENGTH 8000
const char *bug_report_preview_text_md = \
"## Problem Description\n"
"\n"
"Write a detailed description of the problem.\n"
"\n"
"## What is the expected correct behavior?\n"
"\n"
"(What you want to see instead.)\n"
"\n"
"## Remote System Description\n"
"\n"
"* Server (OS name and version):\n"
"* Special notes regarding the remote system (i.e. gateways, tunnel, etc.):\n"
"\n"
"## Debugging\n"
"\n"
"Please check the Include Debug Data box to allow for debug log to be sent with this bug report\n"
"\n"
"## Local System Description\n"
"\n"
"* Client (OS name and version):\n"
"* Remmina version ( ```remmina --version``` ):\n"
"* Installation(s):\n"
" - [ ] Distribution package.\n"
" - [ ] PPA.\n"
" - [ ] Snap.\n"
" - [ ] Flatpak.\n"
" - [ ] Compiled from sources.\n"
" - [ ] Other - detail:\n"
"* Desktop environment (GNOME, Unity, KDE, ..):\n"
"* Plugin(s):\n"
" - [ ] RDP - freerdp version ( ```xfreerdp --version``` ):\n"
" - [ ] VNC\n"
" - [ ] SSH\n"
" - [ ] SFTP\n"
" - [ ] SPICE\n"
" - [ ] WWW\n"
" - [ ] EXEC\n"
" - [ ] Other (please specify):\n"
"* GTK backend (Wayland, Xorg):\n"
"\n";
/* Show the bug report dialog from the file ui/remmina_bug_report.glade */
void remmina_bug_report_open(GtkWindow *parent)
{
TRACE_CALL(__func__);
GtkTextBuffer* bug_report_preview_text_buffer;
remmina_bug_report_dialog = g_new0(RemminaBugReportDialog, 1);
remmina_bug_report_dialog->builder = remmina_public_gtk_builder_new_from_resource ("/org/remmina/Remmina/src/../data/ui/remmina_bug_report.glade");
remmina_bug_report_dialog->dialog = GTK_WIDGET(gtk_builder_get_object(remmina_bug_report_dialog->builder, "RemminaBugReportDialog"));
remmina_bug_report_dialog->bug_report_submit_button = GTK_BUTTON(GET_OBJECT("bug_report_submit_button"));
remmina_bug_report_dialog->bug_report_name_entry = GTK_ENTRY(GET_OBJECT("bug_report_name_entry"));
remmina_bug_report_dialog->bug_report_email_entry = GTK_ENTRY(GET_OBJECT("bug_report_email_entry"));
remmina_bug_report_dialog->bug_report_title_entry = GTK_ENTRY(GET_OBJECT("bug_report_title_entry"));
remmina_bug_report_dialog->bug_report_description_textview = GTK_TEXT_VIEW(GET_OBJECT("bug_report_description_textview"));
remmina_bug_report_dialog->bug_report_submit_status_label = GTK_LABEL(GET_OBJECT("bug_report_submit_status_label"));
remmina_bug_report_dialog->bug_report_debug_data_check_button = GTK_CHECK_BUTTON(GET_OBJECT("bug_report_debug_data_check_button"));
remmina_bug_report_dialog->bug_report_include_system_info_check_button = GTK_CHECK_BUTTON(GET_OBJECT("bug_report_include_system_info_check_button"));
// Set bug report markdown text in bug description window
bug_report_preview_text_buffer = gtk_text_buffer_new(NULL);
gtk_text_buffer_set_text(bug_report_preview_text_buffer, bug_report_preview_text_md, strlen(bug_report_preview_text_md));
gtk_text_view_set_buffer(remmina_bug_report_dialog->bug_report_description_textview, bug_report_preview_text_buffer);
if (parent) {
gtk_window_set_transient_for(GTK_WINDOW(remmina_bug_report_dialog->dialog), parent);
gtk_window_set_destroy_with_parent(GTK_WINDOW(remmina_bug_report_dialog->dialog), TRUE);
}
g_signal_connect(remmina_bug_report_dialog->bug_report_submit_button, "clicked",
G_CALLBACK(remmina_bug_report_dialog_on_action_submit), (gpointer)remmina_bug_report_dialog);
gtk_window_present(GTK_WINDOW(remmina_bug_report_dialog->dialog));
g_object_unref(bug_report_preview_text_buffer);
}
void remmina_bug_report_dialog_on_action_submit(GSimpleAction *action, GVariant *param, gpointer data)
{
TRACE_CALL(__func__);
JsonGenerator *g;
JsonObject *o;
gchar *b;
REMMINA_DEBUG("Submit Button Clicked. Uploading Bug Report data.");
gchar *markup = GRAY_TEXT("Sending Bug Report...");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
// Store bug report info in JSON blob and encode before submitting
JsonNode *bug_report_data = remmina_bug_report_get_all();
if (bug_report_data == NULL || (o = json_node_get_object(bug_report_data)) == NULL) {
REMMINA_DEBUG("Failed to grab bug report data, no request is sent");
gchar *markup = RED_TEXT("Failure: Unable to generate bug report message. Please try again.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
json_node_unref(bug_report_data);
return;
} else if (strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Name"), "") == 0) {
REMMINA_DEBUG("No text in name entry of bug report data, no request is sent");
gchar *markup = RED_TEXT("Failure: Name/Username is required. Please enter a Name/Username.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
json_node_unref(bug_report_data);
return;
} else {
gchar *email_text = g_strdup(json_object_get_string_member(json_node_get_object(bug_report_data), "Email"));
if (strcmp(email_text, "") == 0) {
REMMINA_DEBUG("No text in email entry of bug report data, no request is sent");
gchar *markup = RED_TEXT("Failure: Email is required. Please enter an email.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
g_free(email_text);
json_node_unref(bug_report_data);
return;
} else {
gchar *save_ptr;
gchar *check_for_at_symbol = strtok_r(email_text, "@", &save_ptr);
gchar *check_for_dot = strtok_r(NULL, ".", &save_ptr);
gchar *check_for_domain = strtok_r(NULL, "", &save_ptr);
if (check_for_at_symbol == NULL || check_for_dot == NULL || check_for_domain == NULL) {
REMMINA_DEBUG("Text in email entry of bug report data is not a valid email, no request is sent");
gchar *markup = RED_TEXT("Failure: A valid email is required. Email is missing a prefix or domain.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
g_free(email_text);
json_node_unref(bug_report_data);
return;
} else if (strpbrk(check_for_at_symbol, "@.") != NULL || strpbrk(check_for_dot, "@.") != NULL || strpbrk(check_for_domain, "@.") != NULL) {
REMMINA_DEBUG("Text in email entry of bug report data is not a valid email, no request is sent");
gchar *markup = RED_TEXT("Failure: A valid email is required. Email contains extra @ and . characters.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
g_free(email_text);
json_node_unref(bug_report_data);
return;
}
}
g_free(email_text);
if (strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Bug_Title"), "") == 0) {
REMMINA_DEBUG("No text in bug title entry of bug report data, no request is sent");
gchar *markup = RED_TEXT("Failure: Bug Title is required. Please enter a Bug Title.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
json_node_unref(bug_report_data);
return;
} else if (strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Bug_Description"), "") == 0 ||
strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Bug_Description"), bug_report_preview_text_md) == 0) {
REMMINA_DEBUG("No text in bug description of bug report data, no request is sent");
gchar *markup = RED_TEXT("Failure: Bug Description is required. Please fill out the template.");
gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
g_free(markup);
json_node_unref(bug_report_data);
return;
}
}
g = json_generator_new();
json_generator_set_root(g, bug_report_data);
b = json_generator_to_data(g, NULL);
g_object_unref(g);
remmina_curl_compose_message(b, "POST", BUG_REPORT_UPLOAD_URL, remmina_bug_report_dialog->bug_report_submit_status_label);
json_node_unref(bug_report_data);
}
JsonNode *remmina_bug_report_get_all()
{
TRACE_CALL(__func__);
JsonBuilder *b_inner, *b_outer; // holds entire JSON blob
JsonGenerator *g;
JsonNode *n; // temp JSON node
gchar *unenc_b, *enc_b;
gchar *uid;
// Initialize JSON blob
b_outer = json_builder_new();
if (b_outer == NULL) {
g_object_unref(b_outer);
return NULL;
}
json_builder_begin_object(b_outer);
gboolean include_debug_data = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_bug_report_dialog->bug_report_debug_data_check_button));
gboolean include_system_info = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_bug_report_dialog->bug_report_include_system_info_check_button));
if (include_debug_data || include_system_info) {
b_inner = json_builder_new();
if (b_inner == NULL) {
g_object_unref(b_inner);
g_object_unref(b_outer);
return NULL;
}
json_builder_begin_object(b_inner);
if (include_system_info){
n = remmina_info_stats_get_all();
json_builder_set_member_name(b_outer, "System_Info_Enabled");
json_builder_add_string_value(b_outer, "ON");
json_builder_set_member_name(b_inner, "System_Info");
json_builder_add_value(b_inner, n);
}
else{
json_builder_set_member_name(b_outer, "System_Info_Enabled");
json_builder_add_string_value(b_outer, "OFF");
}
if (include_debug_data){
gboolean log_file_result;
gchar *log_text;
gsize log_length;
GError *log_read_error;
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
log_file_result = g_file_get_contents(log_filename, &log_text, &log_length, &log_read_error);
g_free(log_filename);
if (!log_file_result && log_read_error == NULL) {
REMMINA_DEBUG("Failed to grab entire log text, read error: %s", log_read_error->message);
}
gboolean shorter_logs_result;
gchar *temp_log_filename = g_build_filename(g_get_tmp_dir(), TEMP_LOG_TEXT_FILE_NAME, NULL);
gchar *recent_log_text;
if (log_length <= LOG_RECENT_WINDOW) {
recent_log_text = log_text;
shorter_logs_result = g_file_set_contents(temp_log_filename, recent_log_text, log_length, NULL);
}
else {
recent_log_text = log_text + (log_length - LOG_RECENT_WINDOW);
shorter_logs_result = g_file_set_contents(temp_log_filename, recent_log_text, LOG_RECENT_WINDOW, NULL);
}
g_free(log_text);
if (!shorter_logs_result) {
REMMINA_DEBUG("Failed to write recent window of logs to temp file.");
}
gchar *comp_log_filename = g_build_filename(g_get_tmp_dir(), COMPRESSED_LOG_FILE_NAME, NULL);
GFile *in = g_file_new_for_path(temp_log_filename);
GFile *out = g_file_new_for_path(comp_log_filename);
// Clear out old compressed log file to prevent appending
g_file_delete(out, NULL, NULL);
remmina_compress_from_file_to_file(in, out);
g_free(temp_log_filename);
gboolean com_log_file_result;
gchar *compressed_logs;
gsize com_log_length;
GError *com_log_read_error;
com_log_file_result = g_file_get_contents(comp_log_filename, &compressed_logs, &com_log_length, &com_log_read_error);
n = json_node_alloc();
if (!com_log_file_result && com_log_read_error != NULL) {
REMMINA_DEBUG("Failed to grab entire compressed log text, read error: %s", com_log_read_error->message);
json_node_init_string(n, "");
}
else {
gchar *logs_base64 = g_base64_encode(compressed_logs, com_log_length);
json_node_init_string(n, logs_base64);
g_free(logs_base64);
}
json_builder_set_member_name(b_outer, "Debug_Log_Enabled");
json_builder_add_string_value(b_outer, "ON");
json_builder_set_member_name(b_inner, "Compressed_Logs");
json_builder_add_value(b_inner, n);
g_free(compressed_logs);
g_free(comp_log_filename);
}
else{
json_builder_set_member_name(b_outer, "Debug_Log_Enabled");
json_builder_add_string_value(b_outer, "OFF");
}
// Wrap up by setting n to the final JSON blob
json_builder_end_object(b_inner);
n = json_builder_get_root(b_inner);
g_object_unref(b_inner);
g = json_generator_new();
json_generator_set_root(g, n);
json_node_unref(n);
unenc_b = json_generator_to_data(g, NULL); // unenc_b=bug report data
g_object_unref(g);
// Now base64 encode report data
enc_b = g_base64_encode(unenc_b, strlen(unenc_b));
if (enc_b == NULL) {
g_object_unref(b_outer);
g_free(unenc_b);
REMMINA_DEBUG("Failed to encode inner JSON");
return NULL;
}
g_free(unenc_b);
json_builder_set_member_name(b_outer, "encdata");
json_builder_add_string_value(b_outer, enc_b);
g_free(enc_b);
}
else {
json_builder_set_member_name(b_outer, "System_Info_Enabled");
json_builder_add_string_value(b_outer, "OFF");
json_builder_set_member_name(b_outer, "Debug_Log_Enabled");
json_builder_add_string_value(b_outer, "OFF");
}
n = remmina_info_stats_get_uid();
uid = g_strdup(json_node_get_string(n));
json_builder_set_member_name(b_outer, "UID");
json_builder_add_string_value(b_outer, uid);
// Allocate new json node for each text entry and add to total blob
n = json_node_alloc();
json_node_init_string(n, gtk_entry_get_text(remmina_bug_report_dialog->bug_report_name_entry));
json_builder_set_member_name(b_outer, "Name");
json_builder_add_value(b_outer, n);
n = json_node_alloc();
json_node_init_string(n, gtk_entry_get_text(remmina_bug_report_dialog->bug_report_email_entry));
json_builder_set_member_name(b_outer, "Email");
json_builder_add_value(b_outer, n);
n = json_node_alloc();
json_node_init_string(n, gtk_entry_get_text(remmina_bug_report_dialog->bug_report_title_entry));
json_builder_set_member_name(b_outer, "Bug_Title");
json_builder_add_value(b_outer, n);
GtkTextBuffer *buffer;
gchar *description_text;
GtkTextIter start, end;
buffer = gtk_text_view_get_buffer(remmina_bug_report_dialog->bug_report_description_textview);
gtk_text_buffer_get_start_iter(buffer, &start);
gtk_text_buffer_get_iter_at_offset(buffer, &end, MAX_DESCRIPTION_LENGTH);
description_text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
n = json_node_alloc();
json_node_init_string(n, description_text);
json_builder_set_member_name(b_outer, "Bug_Description");
json_builder_add_value(b_outer, n);
json_builder_end_object(b_outer);
n = json_builder_get_root(b_outer);
g_object_unref(b_outer);
g_free(uid);
return n;
}

60
src/remmina_bug_report.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Remmina - The GTK+ Remote Desktop Client
* Copyright (C) 2009-2011 Vic Lee
* Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
* Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. * If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. * If you
* do not wish to do so, delete this exception statement from your
* version. * If you delete this exception statement from all source
* files in the program, then also delete it here.
*
*/
#pragma once
#include <gtk/gtk.h>
#include "json-glib/json-glib.h"
typedef struct _RemminaBugReportDialog {
GtkBuilder *builder;
GtkWidget *dialog;
GtkButton *bug_report_submit_button;
GtkEntry *bug_report_name_entry;
GtkEntry *bug_report_email_entry;
GtkEntry *bug_report_title_entry;
GtkTextView *bug_report_description_textview;
GtkLabel *bug_report_submit_status_label;
GtkCheckButton *bug_report_debug_data_check_button;
GtkCheckButton *bug_report_include_system_info_check_button;
} RemminaBugReportDialog;
G_BEGIN_DECLS
void remmina_bug_report_open(GtkWindow *parent);
void remmina_bug_report_dialog_on_action_submit(GSimpleAction *action, GVariant *param, gpointer data);
JsonNode *remmina_bug_report_get_all(void);
G_END_DECLS

View File

@ -0,0 +1,265 @@
/*
* Remmina - The GTK+ Remote Desktop Client
* Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. * If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. * If you
* do not wish to do so, delete this exception statement from your
* version. * If you delete this exception statement from all source
* files in the program, then also delete it here.
*
*/
#include <curl/curl.h>
#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
#include "remmina.h"
#include "remmina_info.h"
#include "remmina_log.h"
#include "remmina_plugin_manager.h"
#include "remmina_pref.h"
#include "remmina_curl_connector.h"
#include "remmina/remmina_trace_calls.h"
#include "remmina_utils.h"
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/gdkwayland.h>
#endif
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
extern gboolean info_disable_stats;
extern gboolean info_disable_news;
extern gboolean info_disable_tip;
struct curl_msg {
gchar *body;
gchar *url;
gpointer *user_data;
char* response;
size_t size;
};
enum ShowValue {ShowNews, ShowTip, ShowNone};
// If we receive a response from the server parse out the message
// and call the appropiate functions to handle the message.
void handle_resp(struct curl_msg * message){
gchar *news_checksum = NULL;
JsonParser *parser = NULL;
enum ShowValue to_show = ShowNone;
if (!imode) {
parser = json_parser_new();
if (!parser) {
return;
}
if (!json_parser_load_from_data(parser, message->response , message->size, NULL)){
g_object_unref(parser);
return;
}
JsonReader *reader = json_reader_new(json_parser_get_root(parser));
if (!reader) {
g_object_unref(parser);
return;
}
const gchar *json_tip_string;
const gchar *json_news_string;
gint64 json_stats_int = 0;
if (!json_reader_read_element(reader, 0)) {
g_object_unref(parser);
g_object_unref(reader);
return;
}
if (!json_reader_read_member(reader, "TIP")) {
json_tip_string = NULL;
}
else {
json_tip_string = json_reader_get_string_value(reader);
}
json_reader_end_member(reader);
if (!json_reader_read_member(reader, "NEWS")) {
json_news_string = NULL;
}
else {
json_news_string = json_reader_get_string_value(reader);
}
json_reader_end_member(reader);
if (!json_reader_read_member(reader, "STATS")) {
json_stats_int = 0;
}
else {
json_stats_int = json_reader_get_int_value(reader);
}
json_reader_end_member(reader);
if (json_reader_read_member(reader, "LIST")){
g_idle_add(remmina_plugin_manager_parse_plugin_list, json_reader_new(json_parser_get_root(parser)));
}
json_reader_end_member(reader);
if (json_reader_read_member(reader, "PLUGINS")){
g_idle_add(remmina_plugin_manager_download_plugins, json_reader_new(json_parser_get_root(parser)));
}
json_reader_end_member(reader);
json_reader_end_element(reader);
g_object_unref(reader);
if (json_news_string == NULL) {
if (json_tip_string == NULL) {
to_show = ShowNone;
}
else if (info_disable_tip == 0) {
to_show = ShowTip;
}
}
else {
news_checksum = remmina_sha256_buffer(json_news_string, strlen(json_news_string));
if (news_checksum == NULL) {
REMMINA_DEBUG("News checksum is NULL");
}
else if ((remmina_pref.periodic_news_last_checksum == NULL) || strcmp(news_checksum, remmina_pref.periodic_news_last_checksum) != 0) {
REMMINA_DEBUG("Latest news checksum: %s", news_checksum);
remmina_pref.periodic_news_last_checksum = g_strdup(news_checksum);
remmina_pref_save();
if (info_disable_news == 0) {
to_show = ShowNews;
}
}
if (to_show == ShowNone && json_tip_string != NULL && info_disable_tip == 0) {
to_show = ShowTip;
}
g_free(news_checksum);
}
if (to_show == ShowTip) {
RemminaInfoMessage *message = malloc(sizeof(RemminaInfoMessage));
message->info_string= g_strdup(json_tip_string);
message->title_string= g_strdup("Tip of the Day");
g_idle_add(remmina_info_show_response, message);
}
else if (to_show == ShowNews) {
RemminaInfoMessage *message = malloc(sizeof(RemminaInfoMessage));
message->info_string= g_strdup(json_news_string);
message->title_string= g_strdup("NEWS");
g_idle_add(remmina_info_show_response, message);
}
if (json_stats_int){
remmina_info_stats_collector();
}
g_object_unref(parser);
}
}
// Allocate memory to hold response from the server in msg->response
size_t remmina_curl_process_response(void *buffer, size_t size, size_t nmemb, void *userp){
size_t realsize = size * nmemb;
struct curl_msg *msg = (struct curl_msg *)userp;
char *ptr = realloc(msg->response, msg->size + realsize + 1);
if (!ptr) {
return 0; /* out of memory! */
}
msg->response = ptr;
memcpy(&(msg->response[msg->size]), buffer, realsize);
msg->size += realsize;
msg->response[msg->size] = 0;
return realsize;
}
void remmina_curl_send_message(gpointer data)
{
gchar *result_message = NULL;
gchar *marked_up_message = NULL;
struct curl_msg* message = (struct curl_msg*)data;
message->size = 0;
message->response = NULL;
CURL* curl = curl_easy_init();
if (curl){
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, message->url);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, message->body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, remmina_curl_process_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, message);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60);
res = curl_easy_perform(curl);
if(res != CURLE_OK){
curl_easy_strerror(res);
result_message = "Failure: Transport error occurred - see debug or logs for more information";
marked_up_message = RED_TEXT(result_message);
}
else{
handle_resp(message);
result_message = "Success: Message sent successfully, please wait for report to automatically open a new issue. This may take a little while.";
marked_up_message = GREEN_TEXT(result_message);
}
if (message->user_data != NULL){
gtk_label_set_markup(GTK_LABEL(message->user_data), marked_up_message);
}
if (message->body){
g_free(message->body);
}
if (message->response){
g_free(message->response);
}
g_free(message);
g_free(marked_up_message);
curl_easy_cleanup(curl);
}
}
void remmina_curl_compose_message(gchar* body, char* type, char* url, gpointer data)
{
if (!imode){
struct curl_msg* message = (struct curl_msg*)malloc(sizeof(struct curl_msg));
message->body = body;
message->url = url;
message->user_data = data;
g_thread_new("send_curl_message", (GThreadFunc)remmina_curl_send_message, message);
}
}

View File

@ -31,15 +31,23 @@
* files in the program, then also delete it here.
*
*/
#pragma once
#include <glib.h>
#include "json-glib/json-glib.h"
G_BEGIN_DECLS
#include "json-glib/json-glib.h"
#define DOWNLOAD_URL "https://plugins.remmina.org/plugins/plugin_download"
#define LIST_URL "https://plugins.remmina.org/plugins/get_list"
#define PERIODIC_UPLOAD_URL "https://info.remmina.org/info/upload_stats"
#define INFO_REQUEST_URL "https://info.remmina.org/info/handshake"
JsonNode *remmina_stats_get_all(void);
#define RED_TEXT(str) g_markup_printf_escaped("<span color=\"red\">%s</span>", str)
#define GREEN_TEXT(str) g_markup_printf_escaped("<span color=\"green\">%s</span>", str)
#define GRAY_TEXT(str) g_markup_printf_escaped("<span color=\"gray\">%s</span>", str)
void remmina_curl_handshake(gpointer data);
void remmina_curl_compose_message(gchar* body, char* type, char* url, gpointer data);
G_END_DECLS

View File

@ -33,7 +33,7 @@
*/
/**
* @file remmina_stats.c
* @file remmina_info.c
* @brief Remmina usage statistics module.
* @author Antenore Gatta and Giovanni Panozzo
* @date 12 Feb 2018
@ -142,14 +142,19 @@
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "remmina.h"
#include "remmina_file.h"
#include "remmina_file_manager.h"
#include "remmina_icon.h"
#include "remmina_log.h"
#include "remmina_pref.h"
#include "remmina_curl_connector.h"
#include "remmina_sysinfo.h"
#include "remmina_utils.h"
#include "remmina_scheduler.h"
#include "remmina_public.h"
#include "remmina_main.h"
#include "remmina/remmina_trace_calls.h"
#include "remmina_plugin_manager.h"
@ -159,7 +164,14 @@
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "remmina_stats.h"
#include "remmina_info.h"
#define MAX_ENV_LEN 10000
gboolean info_disable_stats = 1;
gboolean info_disable_news = 1;
gboolean info_disable_tip = 1;
struct ProfilesData {
GHashTable *proto_count;
@ -170,7 +182,69 @@ struct ProfilesData {
gchar datestr;
};
JsonNode *remmina_stats_get_os_info()
#if !JSON_CHECK_VERSION(1, 2, 0)
#define json_node_unref(x) json_node_free(x)
#endif
/* Timers */
#define INFO_PERIODIC_CHECK_1ST_MS 1000
#define INFO_PERIODIC_CHECK_INTERVAL_MS 86400000
#define PERIODIC_UPLOAD_URL "https://info.remmina.org/info/upload_stats"
#define INFO_REQUEST_URL "https://info.remmina.org/info/handshake"
static RemminaInfoDialog *remmina_info_dialog;
#define GET_OBJ(object_name) gtk_builder_get_object(remmina_info_dialog->builder, object_name)
typedef struct {
gboolean send_stats;
JsonNode *statsroot;
} sc_tdata;
JsonNode *remmina_info_stats_get_uid()
{
TRACE_CALL(__func__);
JsonNode *r;
GChecksum *chs;
const gchar *uname, *hname;
const gchar *uid_suffix;
gchar *uid_prefix;
gchar *uid;
/** @warning this function is usually executed on a dedicated thread,
* not on the main thread
*/
if (remmina_pref.info_uid_prefix == NULL || remmina_pref.info_uid_prefix[0] == 0) {
/* Generate a new UUID_PREFIX for this installation */
uid_prefix = remmina_gen_random_uuid();
if (remmina_pref.info_uid_prefix) {
g_free(remmina_pref.info_uid_prefix);
}
remmina_pref.info_uid_prefix = uid_prefix;
remmina_pref_save();
}
uname = g_get_user_name();
hname = g_get_host_name();
chs = g_checksum_new(G_CHECKSUM_SHA256);
g_checksum_update(chs, (const guchar *)uname, strlen(uname));
g_checksum_update(chs, (const guchar *)hname, strlen(hname));
uid_suffix = g_checksum_get_string(chs);
uid = g_strdup_printf("%s-%.10s", remmina_pref.info_uid_prefix, uid_suffix);
g_checksum_free(chs);
r = json_node_alloc();
json_node_init_string(r, uid);
g_free(uid);
return r;
}
JsonNode *remmina_info_stats_get_os_info()
{
TRACE_CALL(__func__);
JsonBuilder *b;
@ -186,15 +260,18 @@ JsonNode *remmina_stats_get_os_info()
gchar *codename;
GHashTableIter iter;
gchar *key, *value;
gchar *mage;
/** @warning this function is usually executed on a dedicated thread,
* not on the main thread */
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "kernel_name");
kernel_name = g_strdup_printf("%s", remmina_utils_get_kernel_name());
kernel_name = remmina_utils_get_kernel_name();
if (!kernel_name || kernel_name[0] == '\0') {
json_builder_add_null_value(b);
}else {
@ -203,7 +280,7 @@ JsonNode *remmina_stats_get_os_info()
g_free(kernel_name);
json_builder_set_member_name(b, "kernel_release");
kernel_release = g_strdup_printf("%s", remmina_utils_get_kernel_release());
kernel_release = remmina_utils_get_kernel_release();
if (!kernel_release || kernel_release[0] == '\0') {
json_builder_add_null_value(b);
}else {
@ -212,7 +289,7 @@ JsonNode *remmina_stats_get_os_info()
g_free(kernel_release);
json_builder_set_member_name(b, "kernel_arch");
kernel_arch = g_strdup_printf("%s", remmina_utils_get_kernel_arch());
kernel_arch = remmina_utils_get_kernel_arch();
if (!kernel_arch || kernel_arch[0] == '\0') {
json_builder_add_null_value(b);
}else {
@ -272,6 +349,12 @@ JsonNode *remmina_stats_get_os_info()
json_builder_add_null_value(b);
}
mage = remmina_utils_get_mage();
json_builder_set_member_name(b, "mage");
json_builder_add_string_value(b, mage);
g_free(mage);
/** @todo Add other means to identify a release name/description
* to cover as much OS as possible, like /etc/issue
*/
@ -288,30 +371,98 @@ JsonNode *remmina_stats_get_os_info()
*
* @return a JSON Node structure containing the users environment.
*/
JsonNode *remmina_stats_get_user_env()
JsonNode *remmina_info_stats_get_user_env()
{
TRACE_CALL(__func__);
JsonBuilder *b;
JsonNode *r;
gchar *language;
gchar **environment;
gchar **uenv;
gchar *str;
gsize env_len = 0;
language = remmina_utils_get_lang();
b = json_builder_new();
if (b == NULL) {
return NULL;
}
environment = g_get_environ();
uenv = environment;
json_builder_begin_object(b);
json_builder_set_member_name(b, "language");
json_builder_add_string_value(b, language);
gchar *safe_ptr;
while (*uenv && env_len <= MAX_ENV_LEN) {
str = strtok_r(*uenv, "=", &safe_ptr);
if (str != NULL) {
env_len += strlen(str);
json_builder_set_member_name(b, str);
}
str = strtok_r(NULL, "\n", &safe_ptr);
if (str != NULL) {
env_len += strlen(str);
json_builder_add_string_value(b, str);
}
else{
json_builder_add_string_value(b, "");
}
uenv++;
}
json_builder_end_object(b);
r = json_builder_get_root(b);
g_object_unref(b);
g_strfreev(environment);
return r;
}
JsonNode *remmina_info_stats_get_host() {
TRACE_CALL(__func__);
JsonBuilder *b;
JsonNode *r;
gchar *logical, *physical;
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "host");
logical = remmina_utils_get_logical();
if (!logical || logical[0] == '\0') {
json_builder_add_null_value(b);
}
else {
json_builder_add_string_value(b, logical);
}
g_free(logical);
json_builder_set_member_name(b, "hw");
physical = remmina_utils_get_link();
if (!physical || physical[0] == '\0') {
json_builder_add_null_value(b);
}
else {
json_builder_add_string_value(b, physical);
}
g_free(physical);
json_builder_end_object(b);
r = json_builder_get_root(b);
g_object_unref(b);
return r;
}
JsonNode *remmina_stats_get_version()
JsonNode *remmina_info_stats_get_version()
{
TRACE_CALL(__func__);
JsonBuilder *b;
@ -322,6 +473,9 @@ JsonNode *remmina_stats_get_version()
* not on the main thread */
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "version");
json_builder_add_string_value(b, VERSION);
@ -353,7 +507,7 @@ JsonNode *remmina_stats_get_version()
return r;
}
JsonNode *remmina_stats_get_gtk_version()
JsonNode *remmina_info_stats_get_gtk_version()
{
TRACE_CALL(__func__);
JsonBuilder *b;
@ -364,6 +518,10 @@ JsonNode *remmina_stats_get_gtk_version()
*/
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "major");
json_builder_add_int_value(b, gtk_get_major_version());
@ -375,10 +533,9 @@ JsonNode *remmina_stats_get_gtk_version()
r = json_builder_get_root(b);
g_object_unref(b);
return r;
}
JsonNode *remmina_stats_get_gtk_backend()
JsonNode *remmina_info_stats_get_gtk_backend()
{
TRACE_CALL(__func__);
JsonNode *r;
@ -410,7 +567,7 @@ JsonNode *remmina_stats_get_gtk_backend()
}
JsonNode *remmina_stats_get_wm_name()
JsonNode *remmina_info_stats_get_wm_name()
{
TRACE_CALL(__func__);
JsonBuilder *b;
@ -419,6 +576,10 @@ JsonNode *remmina_stats_get_wm_name()
gchar *wmname;
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "window_manager");
@ -426,9 +587,7 @@ JsonNode *remmina_stats_get_wm_name()
/** We try to get the GNOME Shell version */
wmver = remmina_sysinfo_get_gnome_shell_version();
if (!wmver || wmver[0] == '\0') {
REMMINA_DEBUG("GNOME Shell not found");
}else {
REMMINA_DEBUG("GNOME Shell version: %s\n", wmver);
json_builder_add_string_value(b, "GNOME Shell");
json_builder_set_member_name(b, "gnome_shell_ver");
json_builder_add_string_value(b, wmver);
@ -438,11 +597,8 @@ JsonNode *remmina_stats_get_wm_name()
wmname = remmina_sysinfo_get_wm_name();
if (!wmname || wmname[0] == '\0') {
/** When everything else fails with set the WM name to NULL **/
REMMINA_DEBUG("Cannot determine the window manger name");
json_builder_add_string_value(b, "n/a");
}else {
REMMINA_DEBUG("Window manger names %s", wmname);
json_builder_add_string_value(b, wmname);
}
g_free(wmname);
@ -454,7 +610,7 @@ JsonNode *remmina_stats_get_wm_name()
return r;
}
JsonNode *remmina_stats_get_indicator()
JsonNode *remmina_info_stats_get_indicator()
{
TRACE_CALL(__func__);
JsonBuilder *b;
@ -462,6 +618,10 @@ JsonNode *remmina_stats_get_indicator()
gboolean sni; /** Support for StatusNotifier or AppIndicator */
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "appindicator_supported");
@ -478,8 +638,11 @@ JsonNode *remmina_stats_get_indicator()
json_builder_add_int_value(b, 0);
#endif
}
/** StatusNotifier/Appindicator NOT supported by desktop */
json_builder_add_int_value(b, 0);
else{
/** StatusNotifier/Appindicator NOT supported by desktop */
json_builder_add_int_value(b, 0);
}
json_builder_set_member_name(b, "icon_is_active");
if (remmina_icon_is_available()) {
/** Remmina icon is active */
@ -508,7 +671,7 @@ JsonNode *remmina_stats_get_indicator()
* This is used as a callback function with remmina_file_manager_iterate.
* @todo Move this in a separate file.
*/
static void remmina_profiles_get_data(RemminaFile *remminafile, gpointer user_data)
static void remmina_info_profiles_get_data(RemminaFile *remminafile, gpointer user_data)
{
TRACE_CALL(__func__);
@ -527,7 +690,6 @@ static void remmina_profiles_get_data(RemminaFile *remminafile, gpointer user_da
pdata->protocol = remmina_file_get_string(remminafile, "protocol");
//pdata->pdatestr = remmina_file_get_string(remminafile, "last_success");
const gchar *last_success = remmina_file_get_string(remminafile, "last_success");
g_debug("%s date %s", pdata->protocol, last_success);
prof_gdate = pdata_gdate = NULL;
if (last_success && last_success[0] != '\0' && strlen(last_success) >= 6) {
@ -572,32 +734,26 @@ static void remmina_profiles_get_data(RemminaFile *remminafile, gpointer user_da
/** When both date in the hash and in the profile are valid we compare the date */
if (prof_gdate != NULL && pdata_gdate != NULL ) {
g_debug("Comparing dates");
gint res = g_date_time_compare( pdata_gdate, prof_gdate );
/** If the date in the hash less than the date in the profile, we take the latter */
if (res < 0 ) {
g_debug("hash date is less than profile date. Replacing date in the hashtable");
g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(last_success));
} else {
g_debug("profile date is less than hash date. Replacing date in the hashtable");
g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
}
}
/** If the date in the profile is NOT valid and the date in the hash is valid we keep the latter */
if (prof_gdate == NULL && pdata_gdate != NULL) {
g_debug("prof_gdate is NULL, replacing date in the hashtable");
g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
}
/** If the date in the hash is NOT valid and the date in the profile is valid we keep the latter */
if (prof_gdate != NULL && pdata_gdate == NULL) {
g_debug("pdata_gdate is NULL, replacing date in the hashtable");
g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(last_success));
}
/** If both date are NULL, we insert NULL for that protocol */
if ((prof_gdate == NULL && pdata_gdate == NULL) && pdata->pdatestr) {
g_debug("All dates are NULL, replacing date in the hashtable");
g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), NULL);
}
} else {
@ -611,7 +767,6 @@ static void remmina_profiles_get_data(RemminaFile *remminafile, gpointer user_da
}
}
}
g_debug("pdata set to %s protocol with last_success to %s", pdata->protocol, pdata->pdatestr);
if (pdata_gdate)
g_date_time_unref(pdata_gdate);
if (prof_gdate)
@ -641,7 +796,7 @@ static void remmina_profiles_get_data(RemminaFile *remminafile, gpointer user_da
* @return a JSON Node structure containing the protocol usage statistics.
*
*/
JsonNode *remmina_stats_get_profiles()
JsonNode *remmina_info_stats_get_profiles()
{
TRACE_CALL(__func__);
@ -658,6 +813,10 @@ JsonNode *remmina_stats_get_profiles()
pdata = g_malloc0(sizeof(struct ProfilesData));
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "profile_count");
@ -671,9 +830,8 @@ JsonNode *remmina_stats_get_profiles()
(GDestroyNotify)g_free, NULL);
profiles_count = remmina_file_manager_iterate(
(GFunc)remmina_profiles_get_data,
(GFunc)remmina_info_profiles_get_data,
(gpointer)pdata);
g_debug("Number of profiles: %d", profiles_count);
json_builder_add_int_value(b, profiles_count);
@ -686,11 +844,9 @@ JsonNode *remmina_stats_get_profiles()
g_hash_table_iter_init(&pdateiter, pdata->proto_date);
while (g_hash_table_iter_next(&pdateiter, &pdatekey, &pdatevalue)) {
s = g_strdup_printf("DATE_%s", (gchar*)pdatekey);
g_debug("Protocol date label: %s", s);
json_builder_set_member_name(b, s);
g_free(s);
json_builder_add_string_value(b, (gchar*)pdatevalue);
g_debug("Protocol date: %s", (gchar*)pdatevalue);
}
json_builder_end_object(b);
@ -713,7 +869,7 @@ JsonNode *remmina_stats_get_profiles()
* @return a JSON Node structure containing the secret plugin in use
*
*/
JsonNode *remmina_stats_get_secret_plugin()
JsonNode *remmina_info_stats_get_secret_plugin()
{
TRACE_CALL(__func__);
@ -723,6 +879,10 @@ JsonNode *remmina_stats_get_secret_plugin()
secret_plugin = remmina_plugin_manager_get_secret_plugin();
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
if (secret_plugin && secret_plugin->is_service_available) {
@ -742,7 +902,7 @@ JsonNode *remmina_stats_get_secret_plugin()
* @return a JSON Node structure containing the status of the primary password
*
*/
JsonNode *remmina_stats_get_primary_password_status()
JsonNode *remmina_info_stats_get_primary_password_status()
{
TRACE_CALL(__func__);
@ -750,6 +910,10 @@ JsonNode *remmina_stats_get_primary_password_status()
JsonNode *r;
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "primary_password_status");
@ -772,7 +936,7 @@ JsonNode *remmina_stats_get_primary_password_status()
* @return a JSON Node structure containing the status of the primary password
*
*/
JsonNode *remmina_stats_get_kiosk_mode()
JsonNode *remmina_info_stats_get_kiosk_mode()
{
TRACE_CALL(__func__);
@ -780,6 +944,10 @@ JsonNode *remmina_stats_get_kiosk_mode()
JsonNode *r;
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "kiosk_status");
@ -796,75 +964,357 @@ JsonNode *remmina_stats_get_kiosk_mode()
return r;
}
JsonNode *remmina_info_stats_get_python()
{
TRACE_CALL(__func__);
JsonBuilder *b;
JsonNode *r;
gchar *version;
version = remmina_utils_get_python();
b = json_builder_new();
if(b != NULL)
{
json_builder_begin_object(b);
json_builder_set_member_name(b, "version");
if (!version || version[0] == '\0') {
json_builder_add_null_value(b);
} else {
json_builder_add_string_value(b, version);
}
json_builder_end_object(b);
r = json_builder_get_root(b);
g_object_unref(b);
return r;
}
else
{
return NULL;
}
}
/**
* Get all statistics in JSON format to send periodically to the PHP server.
* Get all statistics in JSON format to send periodically to the server.
* The caller should free the returned buffer with g_free()
* @warning This function is usually executed on a dedicated thread,
* not on the main thread.
* @return a pointer to the JSON string.
*/
JsonNode *remmina_stats_get_all()
JsonNode *remmina_info_stats_get_all()
{
TRACE_CALL(__func__);
JsonBuilder *b;
JsonNode *n;
b = json_builder_new();
if (b == NULL) {
return NULL;
}
json_builder_begin_object(b);
n = remmina_stats_get_version();
n = remmina_info_stats_get_uid();
json_builder_set_member_name(b, "UID");
json_builder_add_value(b, n);
n = remmina_info_stats_get_version();
json_builder_set_member_name(b, "REMMINAVERSION");
json_builder_add_value(b, n);
n = remmina_stats_get_os_info();
n = remmina_info_stats_get_os_info();
json_builder_set_member_name(b, "SYSTEM");
json_builder_add_value(b, n);
/**
* The section ENVIRONMENT collect all the users environment related
* settings.
*/
n = remmina_stats_get_user_env();
n = remmina_info_stats_get_user_env();
json_builder_set_member_name(b, "ENVIRONMENT");
json_builder_add_value(b, n);
n = remmina_stats_get_gtk_version();
n = remmina_info_stats_get_gtk_version();
json_builder_set_member_name(b, "GTKVERSION");
json_builder_add_value(b, n);
n = remmina_stats_get_gtk_backend();
n = remmina_info_stats_get_gtk_backend();
json_builder_set_member_name(b, "GTKBACKEND");
json_builder_add_value(b, n);
n = remmina_stats_get_wm_name();
n = remmina_info_stats_get_wm_name();
json_builder_set_member_name(b, "WINDOWMANAGER");
json_builder_add_value(b, n);
n = remmina_stats_get_indicator();
n = remmina_info_stats_get_indicator();
json_builder_set_member_name(b, "APPINDICATOR");
json_builder_add_value(b, n);
n = remmina_stats_get_profiles();
n = remmina_info_stats_get_profiles();
json_builder_set_member_name(b, "PROFILES");
json_builder_add_value(b, n);
n = remmina_stats_get_secret_plugin();
n = remmina_info_stats_get_secret_plugin();
json_builder_set_member_name(b, "ACTIVESECRETPLUGIN");
json_builder_add_value(b, n);
n = remmina_stats_get_primary_password_status();
n = remmina_info_stats_get_primary_password_status();
json_builder_set_member_name(b, "HASPRIMARYPASSWORD");
json_builder_add_value(b, n);
n = remmina_stats_get_kiosk_mode();
n = remmina_info_stats_get_kiosk_mode();
json_builder_set_member_name(b, "KIOSK");
json_builder_add_value(b, n);
n = remmina_info_stats_get_python();
json_builder_set_member_name(b, "PYTHON");
json_builder_add_value(b, n);
n = remmina_info_stats_get_host();
json_builder_set_member_name(b, "HOST");
json_builder_add_value(b, n);
json_builder_end_object(b);
n = json_builder_get_root(b);
g_object_unref(b);
return n;
}
void remmina_info_schedule()
{
sc_tdata *data;
data = g_malloc(sizeof(sc_tdata));
data->send_stats = !info_disable_stats;
remmina_scheduler_setup(remmina_info_periodic_check,
data,
INFO_PERIODIC_CHECK_1ST_MS,
INFO_PERIODIC_CHECK_INTERVAL_MS);
}
static void remmina_info_close_clicked(GtkButton *btn, gpointer user_data)
{
TRACE_CALL(__func__);
if (remmina_info_dialog->dialog) {
gtk_widget_destroy(GTK_WIDGET(remmina_info_dialog->dialog));
}
remmina_info_dialog->dialog = NULL;
g_free(remmina_info_dialog);
remmina_info_dialog = NULL;
}
static gboolean remmina_info_dialog_deleted(GtkButton *btn, gpointer user_data)
{
TRACE_CALL(__func__);
gtk_widget_destroy(GTK_WIDGET(remmina_info_dialog->dialog));
remmina_info_dialog->dialog = NULL;
g_free(remmina_info_dialog);
remmina_info_dialog = NULL;
return FALSE;
}
gboolean remmina_info_show_response(gpointer user_data)
{
TRACE_CALL(__func__);
GtkWindow * parent = remmina_main_get_window();
remmina_info_dialog = g_new0(RemminaInfoDialog, 1);
remmina_info_dialog->retval = 1;
RemminaInfoMessage *message = (RemminaInfoMessage*)user_data;
remmina_info_dialog->builder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_info.glade");
remmina_info_dialog->dialog = GTK_DIALOG(gtk_builder_get_object(remmina_info_dialog->builder, "RemminaInfoDialog"));
remmina_info_dialog->remmina_info_text_view = GTK_TEXT_VIEW(GET_OBJ("remmina_info_text_view"));
remmina_info_dialog->remmina_info_label = GTK_LABEL(GET_OBJ("remmina_info_label"));
remmina_info_dialog->remmina_info_button_close = GTK_BUTTON(GET_OBJ("remmina_info_button_close"));
gtk_widget_set_can_default(GTK_WIDGET(remmina_info_dialog->remmina_info_button_close), TRUE);
gtk_widget_grab_default(GTK_WIDGET(remmina_info_dialog->remmina_info_button_close));
gtk_label_set_markup(remmina_info_dialog->remmina_info_label, message->info_string);
g_signal_connect(remmina_info_dialog->remmina_info_button_close, "clicked",
G_CALLBACK(remmina_info_close_clicked), (gpointer)remmina_info_dialog);
g_signal_connect(remmina_info_dialog->dialog, "close",
G_CALLBACK(remmina_info_close_clicked), NULL);
g_signal_connect(remmina_info_dialog->dialog, "delete-event",
G_CALLBACK(remmina_info_dialog_deleted), NULL);
/* Connect signals */
gtk_builder_connect_signals(remmina_info_dialog->builder, NULL);
/* Show the non-modal news dialog */
gtk_widget_show_all(GTK_WIDGET(remmina_info_dialog->dialog));
gtk_window_present(GTK_WINDOW(remmina_info_dialog->dialog));
if (parent) {
gtk_window_set_transient_for(GTK_WINDOW(remmina_info_dialog->dialog), parent);
}
gtk_window_set_modal(GTK_WINDOW(remmina_info_dialog->dialog), TRUE);
gtk_window_set_title(GTK_WINDOW(remmina_info_dialog->dialog), message->title_string);
g_free(message);
return FALSE;
}
/**
* Post request to info server
*
* @return gboolean
*/
static gboolean remmina_info_stats_collector_done(gpointer data)
{
JsonNode *n;
JsonGenerator *g;
gchar *unenc_s, *enc_s;
JsonBuilder *b;
JsonObject *o;
gchar *uid;
JsonNode *root;
root = (JsonNode *)data;
if (root == NULL) {
return G_SOURCE_REMOVE;
}
n = root;
if ((o = json_node_get_object(n)) == NULL) {
g_free(data);
return G_SOURCE_REMOVE;
}
uid = g_strdup(json_object_get_string_member(o, "UID"));
g = json_generator_new();
json_generator_set_root(g, n);
json_node_unref(n);
unenc_s = json_generator_to_data(g, NULL);
g_object_unref(g);
EVP_PKEY *remmina_pubkey = remmina_get_pubkey(RSA_KEYTYPE, remmina_RSA_PubKey_v1);
if (remmina_pubkey == NULL) {
g_free(uid);
g_free(unenc_s);
return G_SOURCE_REMOVE;
}
enc_s = remmina_rsa_encrypt_string(remmina_pubkey, unenc_s);
if (enc_s == NULL) {
g_free(uid);
g_free(unenc_s);
return G_SOURCE_REMOVE;
}
b = json_builder_new();
if (b == NULL) {
return G_SOURCE_REMOVE;
}
EVP_PKEY_free(remmina_pubkey);
json_builder_begin_object(b);
json_builder_set_member_name(b, "keyversion");
json_builder_add_int_value(b, 1);
json_builder_set_member_name(b, "encdata");
json_builder_add_string_value(b, enc_s);
json_builder_set_member_name(b, "UID");
json_builder_add_string_value(b, uid);
json_builder_set_member_name(b, "news_enabled");
json_builder_add_int_value(b, TRUE ? 0 : 1);
json_builder_set_member_name(b, "stats_enabled");
json_builder_add_int_value(b, TRUE ? 0 : 1);
json_builder_set_member_name(b, "tip_enabled");
json_builder_add_int_value(b, TRUE ? 0 : 1);
json_builder_end_object(b);
n = json_builder_get_root(b);
g_free(unenc_s);
g_object_unref(b);
g_free(uid);
g_free(enc_s);
g = json_generator_new();
json_generator_set_root(g, n);
enc_s = json_generator_to_data(g, NULL);
g_object_unref(g);
remmina_curl_compose_message(enc_s, "POST", PERIODIC_UPLOAD_URL, NULL);
json_node_unref(n);
return G_SOURCE_REMOVE;
}
/**
* Collect stats and notify when collection is done
*
* @return gpointer
*/
gpointer remmina_info_stats_collector()
{
JsonNode *n;
n = remmina_info_stats_get_all();
/* Stats collecting is done. Notify main thread calling
* remmina_info_stats_collector_done() */
g_idle_add(remmina_info_stats_collector_done, n);
return NULL;
}
void remmina_info_request(gpointer data)
{
// send initial handshake here, in a callback decide how to handle response
JsonNode *n;
JsonGenerator *g;
gchar *enc_s;
JsonBuilder *b;
const gchar *uid = "000000";
n = remmina_info_stats_get_uid();
if (n != NULL){
uid = json_node_get_string(n);
}
b = json_builder_new();
if (b == NULL) {
return;
}
json_builder_begin_object(b);
json_builder_set_member_name(b, "keyversion");
json_builder_add_int_value(b, 1);
json_builder_set_member_name(b, "UID");
json_builder_add_string_value(b, uid);
json_builder_set_member_name(b, "news_enabled");
json_builder_add_int_value(b, info_disable_news ? 0 : 1);
json_builder_set_member_name(b, "stats_enabled");
json_builder_add_int_value(b, info_disable_stats ? 0 : 1);
json_builder_set_member_name(b, "tip_enabled");
json_builder_add_int_value(b, info_disable_tip ? 0 : 1);
json_builder_end_object(b);
n = json_builder_get_root(b);
g_object_unref(b);
g = json_generator_new();
json_generator_set_root(g, n);
enc_s = json_generator_to_data(g, NULL);
g_object_unref(g);
remmina_curl_compose_message(enc_s, "POST", INFO_REQUEST_URL, NULL);
json_node_unref(n);
}
gboolean remmina_info_periodic_check(gpointer user_data)
{
remmina_info_request(user_data);
return G_SOURCE_CONTINUE;
}

70
src/remmina_info.h Normal file
View File

@ -0,0 +1,70 @@
/*
* Remmina - The GTK+ Remote Desktop Client
* Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. * If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. * If you
* do not wish to do so, delete this exception statement from your
* version. * If you delete this exception statement from all source
* files in the program, then also delete it here.
*
*/
#pragma once
#include <glib.h>
#include "json-glib/json-glib.h"
G_BEGIN_DECLS
JsonNode *remmina_info_stats_get_all(void);
JsonNode *remmina_info_stats_get_os_info(void);
JsonNode *remmina_info_stats_get_python(void);
JsonNode *remmina_info_stats_get_uid(void);
gboolean remmina_info_show_response(gpointer user_data);
gpointer remmina_info_stats_collector();
gboolean remmina_info_periodic_check(gpointer user_data);
void remmina_info_schedule(void);
typedef struct _RemminaInfoDialog
{
GtkBuilder *builder;
GtkDialog *dialog;
GtkTextView *remmina_info_text_view;
GtkLabel *remmina_info_label;
GtkButton *remmina_info_button_close;
gint retval;
} RemminaInfoDialog;
typedef struct _RemminaInfoMessage
{
const gchar *info_string;
const gchar *title_string;
} RemminaInfoMessage;
G_END_DECLS

View File

@ -39,7 +39,7 @@
#include "remmina_public.h"
#include "remmina_pref.h"
#include "remmina_log.h"
#include "remmina_stats.h"
#include "remmina_info.h"
#include "remmina/remmina_trace_calls.h"
gboolean logstart;
@ -73,7 +73,7 @@ void remmina_log_stats()
TRACE_CALL(__func__);
JsonNode *n;
n = remmina_stats_get_all();
n = remmina_info_stats_get_all();
if (n != NULL) {
JsonGenerator *g = json_generator_new();
@ -213,6 +213,18 @@ void _remmina_info(const gchar *fmt, ...)
text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
// always appends newline
g_info ("%s", text);
@ -237,6 +249,18 @@ void _remmina_message(const gchar *fmt, ...)
text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
// always appends newline
g_message ("%s", text);
@ -253,7 +277,7 @@ void _remmina_message(const gchar *fmt, ...)
/**
* Print a string in the Remmina Debug Windows and in the terminal.
* The string will be visible in the terminal if G_MESSAGES_DEBUG=all
* The string will be visible in the terminal if G_MESSAGES_DEBUG=remmina
* Variadic function of REMMINA_DEBUG
*/
void _remmina_debug(const gchar *fun, const gchar *fmt, ...)
@ -266,6 +290,18 @@ void _remmina_debug(const gchar *fun, const gchar *fmt, ...)
text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
g_free(text);
@ -293,6 +329,18 @@ void _remmina_warning(const gchar *fun, const gchar *fmt, ...)
text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
g_free(text);
@ -318,6 +366,18 @@ void _remmina_audit(const gchar *fun, const gchar *fmt, ...)
gchar *text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
#if GLIB_CHECK_VERSION(2,62,0)
GDateTime* tv = g_date_time_new_now_local();
gchar *isodate = g_date_time_format_iso8601(tv);
@ -362,6 +422,18 @@ void _remmina_error(const gchar *fun, const gchar *fmt, ...)
text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
g_free(text);
@ -389,6 +461,18 @@ void _remmina_critical(const gchar *fun, const gchar *fmt, ...)
text = g_strdup_vprintf(fmt, args);
va_end(args);
gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
FILE *log_file = fopen(log_filename, "a");
g_free(log_filename);
if (log_file != NULL)
{
gchar* text_log = g_strconcat(text, "\n", NULL);
fwrite(text_log, sizeof(char), strlen(text_log), log_file);
fclose(log_file);
g_free(text_log);
}
g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
g_free(text);

View File

@ -40,6 +40,7 @@
G_BEGIN_DECLS
#define LOG_FILE_NAME "remmina_log_file.log"
#define REMMINA_INFO(fmt, ...) _remmina_info(fmt, ## __VA_ARGS__)
#define REMMINA_MESSAGE(fmt, ...) _remmina_message(fmt, ## __VA_ARGS__)
#define REMMINA_DEBUG(fmt, ...) _remmina_debug(__func__, fmt, ## __VA_ARGS__)

View File

@ -57,6 +57,7 @@
#include "remmina_pref_dialog.h"
#include "remmina_widget_pool.h"
#include "remmina_plugin_manager.h"
#include "remmina_bug_report.h"
#include "remmina_log.h"
#include "remmina_icon.h"
#include "remmina_main.h"
@ -99,6 +100,7 @@ static GActionEntry app_actions[] = {
{ "mpchange", remmina_main_on_action_application_mpchange, NULL, NULL, NULL },
{ "plugins", remmina_main_on_action_application_plugins, NULL, NULL, NULL },
{ "preferences", remmina_main_on_action_application_preferences, "i", NULL, NULL },
{ "bug_report", remmina_main_on_action_application_bug_report, NULL, NULL, NULL},
{ "dark", remmina_main_on_action_application_dark_theme, NULL, NULL, NULL },
{ "debug", remmina_main_on_action_help_debug, NULL, NULL, NULL },
{ "community", remmina_main_on_action_help_community, NULL, NULL, NULL },
@ -855,6 +857,7 @@ void remmina_main_on_action_connection_new(GSimpleAction *action, GVariant *para
return;
GtkWidget *widget;
remmina_plugin_manager_get_available_plugins();
if (remmina_pref_get_boolean("use_primary_password")
&& remmina_pref_get_boolean("lock_edit")
&& remmina_unlock_new(remminamain->window) == 0)
@ -1111,7 +1114,7 @@ void remmina_main_on_action_application_preferences(GSimpleAction *action, GVari
GtkWidget *widget = remmina_pref_dialog_new(tab_num, remminamain->window);
gtk_widget_show_all(widget);
gtk_widget_show(widget);
}
void remmina_main_on_action_application_default(GSimpleAction *action, GVariant *param, gpointer data)
@ -1334,6 +1337,7 @@ void remmina_main_on_action_tools_export(GSimpleAction *action, GVariant *param,
void remmina_main_on_action_application_plugins(GSimpleAction *action, GVariant *param, gpointer data)
{
TRACE_CALL(__func__);
remmina_plugin_manager_get_available_plugins();
remmina_plugin_manager_show(remminamain->window);
}
@ -1389,6 +1393,12 @@ void remmina_main_on_action_application_about(GSimpleAction *action, GVariant *p
remmina_about_open(remminamain->window);
};
void remmina_main_on_action_application_bug_report(GSimpleAction *action, GVariant *param, gpointer data)
{
TRACE_CALL(__func__);
remmina_bug_report_open(remminamain->window);
};
static gboolean is_empty(const gchar *s)
{
if (s == NULL)

View File

@ -115,6 +115,7 @@ void remmina_main_save_before_destroy(void);
void remmina_main_show_dialog(GtkMessageType msg, GtkButtonsType buttons, const gchar* message);
void remmina_main_show_warning_dialog(const gchar *message);
void remmina_main_on_action_application_about(GSimpleAction *action, GVariant *param, gpointer data);
void remmina_main_on_action_application_bug_report(GSimpleAction *action, GVariant *param, gpointer data);
void remmina_main_on_action_application_default(GSimpleAction *action, GVariant *param, gpointer data);
void remmina_main_on_action_application_mpchange(GSimpleAction *action, GVariant *param, gpointer data);
void remmina_main_on_action_application_plugins(GSimpleAction *action, GVariant *param, gpointer data);

File diff suppressed because it is too large Load Diff

View File

@ -37,25 +37,49 @@
#pragma once
#include "remmina/plugin.h"
#include "json-glib/json-glib.h"
#define DOWNLOAD_URL "https://plugins.remmina.org/plugins/plugin_download"
#define LIST_URL "https://plugins.remmina.org/plugins/get_list"
#define ON_DOWNLOAD 1
#define ON_CLOSE 2
#define FAIL 0
#define PARTIAL_SUCCESS 1
#define SUCCESS 2
#define MAX_PLUGIN_NAME_SIZE 100
#define MAX_PLUGINS_ALLOWED_TO_DOWNLOAD 20
G_BEGIN_DECLS
typedef gboolean (*RemminaPluginFunc)(gchar *name, RemminaPlugin *plugin, gpointer data);
void remmina_plugin_manager_init(void);
JsonNode *remmina_plugin_manager_get_installed_plugins(void);
RemminaPlugin *remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name);
gboolean remmina_plugin_manager_query_feature_by_type(RemminaPluginType ptype, const gchar *name, RemminaProtocolFeatureType ftype);
void remmina_plugin_manager_for_each_plugin(RemminaPluginType type, RemminaPluginFunc func, gpointer data);
void remmina_plugin_manager_show(GtkWindow *parent);
void remmina_plugin_manager_for_each_plugin_stdout(RemminaPluginType type, RemminaPluginFunc func, gpointer data);
void remmina_plugin_manager_show_stdout();
void* remmina_plugin_manager_get_available_plugins();
gboolean remmina_plugin_manager_parse_plugin_list(gpointer user_data);
gboolean remmina_plugin_manager_download_plugins(gpointer user_data);
RemminaFilePlugin *remmina_plugin_manager_get_import_file_handler(const gchar *file);
RemminaFilePlugin *remmina_plugin_manager_get_export_file_handler(RemminaFile *remminafile);
RemminaSecretPlugin *remmina_plugin_manager_get_secret_plugin(void);
const gchar *remmina_plugin_manager_get_canonical_setting_name(const RemminaProtocolSetting *setting);
gboolean remmina_plugin_manager_is_encrypted_setting(RemminaProtocolPlugin *pp, const char *setting);
gboolean remmina_gtksocket_available();
gboolean remmina_plugin_manager_verify_duplicate_plugins(RemminaPlugin *test_plugin);
void remmina_plugin_manager_toggle_checkbox(GtkCellRendererToggle *cell, gchar *path, GtkListStore *model);
void remmina_plugin_manager_on_response(GtkDialog *dialog, gint response_id, gpointer user_data);
void remmina_append_json_objects_from_response_str(JsonReader* response_str, GArray* data_array);
guint remmina_plugin_manager_deserialize_plugin_response(GArray *name_array);
gboolean remmina_attempt_to_write_plugin_data_to_disk(RemminaServerPluginResponse *plugin);
GFile* remmina_create_plugin_file(const gchar* plugin_name, const gchar* plugin_version);
JsonNode *remmina_plugin_manager_plugin_stats_get_all(void);
extern RemminaPluginService remmina_plugin_manager_service;
typedef gboolean (*RemminaPluginLoaderFunc)(RemminaPluginService*, const gchar* name);
@ -65,6 +89,13 @@ typedef struct {
RemminaPluginLoaderFunc func;
} RemminaPluginLoader;
typedef struct {
GtkWidget *label;
GtkListStore *store;
GtkWidget *spinner;
gboolean downloading;
} SignalData;
gboolean remmina_plugin_manager_loader_supported(const char *filetype);
void remmina_plugin_manager_add_loader(char *filetype, RemminaPluginLoaderFunc func);

View File

@ -58,6 +58,10 @@
const gchar *default_resolutions = "640x480,800x600,1024x768,1152x864,1280x960,1400x1050,1920x1080";
const gchar *default_keystrokes = "Send hello world§hello world\\n";
extern gboolean info_disable_stats;
extern gboolean info_disable_news;
extern gboolean info_disable_tip;
gchar *remmina_keymap_file;
static GHashTable *remmina_keymap_table = NULL;
@ -217,6 +221,9 @@ void remmina_pref_file_load_colors(GKeyFile *gkeyfile, RemminaColorPref *color_p
}
}
extern gboolean disablenews;
extern gboolean disablestats;
void remmina_pref_init(void)
{
TRACE_CALL(__func__);
@ -751,9 +758,72 @@ void remmina_pref_init(void)
else
remmina_pref.vte_shortcutkey_search_text = GDK_KEY_g;
remmina_pref_file_load_colors(gkeyfile, &remmina_pref.color_pref);
if (g_key_file_has_key(gkeyfile, "remmina_info", "periodic_news_last_checksum", NULL)) {
remmina_pref.periodic_news_last_checksum = g_key_file_get_string(gkeyfile, "remmina_info", "periodic_news_last_checksum", NULL);
}
else {
remmina_pref.periodic_news_last_checksum = NULL;
}
if (disablenews) {
info_disable_news = 1;
}
else if (g_key_file_has_key(gkeyfile, "remmina_info", "periodic_news_permitted", NULL)) {
remmina_pref.disable_news = !g_key_file_get_boolean(gkeyfile, "remmina_info", "periodic_news_permitted", NULL);
info_disable_news = remmina_pref.disable_news;
}
else {
remmina_pref.disable_news = TRUE;
info_disable_news = remmina_pref.disable_news;
}
if (disablestats) {
info_disable_stats = 1;
}
else if (g_key_file_has_key(gkeyfile, "remmina_info", "periodic_usage_stats_permitted", NULL)) {
remmina_pref.disable_stats = !g_key_file_get_boolean(gkeyfile, "remmina_info", "periodic_usage_stats_permitted", NULL);
info_disable_stats = remmina_pref.disable_stats;
}
else {
remmina_pref.disable_stats = TRUE;
info_disable_stats = remmina_pref.disable_stats;
}
if (g_key_file_has_key(gkeyfile, "remmina_info", "disable_tip", NULL)) {
remmina_pref.disable_tip = g_key_file_get_boolean(gkeyfile, "remmina_info", "disable_tip", NULL);
info_disable_tip = remmina_pref.disable_tip;
}
else {
remmina_pref.disable_tip = TRUE;
info_disable_tip = remmina_pref.disable_tip;
}
#ifdef DISABLE_NEWS
info_disable_news = 1;
remmina_pref.disable_news = TRUE;
#endif
#ifdef DISABLE_STATS
info_disable_stats = 1;
remmina_pref.disable_stats = TRUE;
#endif
#ifdef DISABLE_TIP
info_disable_tip = 1;
remmina_pref.disable_tip = TRUE;
#endif
if (g_key_file_has_key(gkeyfile, "remmina_info", "info_uid_prefix", NULL)) {
remmina_pref.info_uid_prefix = g_key_file_get_string(gkeyfile, "remmina_info", "info_uid_prefix", NULL);
}
else {
remmina_pref.info_uid_prefix = NULL;
}
/* If we have a color scheme file, we switch to it, GIO will merge it in the
* remmina.pref file */
if (g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR)) {
@ -921,6 +991,11 @@ gboolean remmina_pref_save(void)
g_key_file_set_string(gkeyfile, "ssh_colors", "color13", remmina_pref.color_pref.color13 ? remmina_pref.color_pref.color13 : "");
g_key_file_set_string(gkeyfile, "ssh_colors", "color14", remmina_pref.color_pref.color14 ? remmina_pref.color_pref.color14 : "");
g_key_file_set_string(gkeyfile, "ssh_colors", "color15", remmina_pref.color_pref.color15 ? remmina_pref.color_pref.color15 : "");
g_key_file_set_boolean(gkeyfile, "remmina_info", "periodic_news_permitted", !remmina_pref.disable_news);
g_key_file_set_string(gkeyfile, "remmina_info", "periodic_news_last_checksum", remmina_pref.periodic_news_last_checksum ? remmina_pref.periodic_news_last_checksum: "");
g_key_file_set_boolean(gkeyfile, "remmina_info", "periodic_usage_stats_permitted", !remmina_pref.disable_stats);
g_key_file_set_string(gkeyfile, "remmina_info", "info_uid_prefix", remmina_pref.info_uid_prefix ? remmina_pref.info_uid_prefix : "");
g_key_file_set_boolean(gkeyfile, "remmina_info", "disable_tip", remmina_pref.disable_tip);
/* Default settings */
g_key_file_set_string(gkeyfile, "remmina", "name", "");

View File

@ -236,7 +236,14 @@ typedef struct _RemminaPref {
RemminaColorPref color_pref;
/* Usage stats */
gchar * last_success;
gboolean disable_stats;
gchar * info_uid_prefix;
/* Remmina news */
gboolean disable_news;
gchar * periodic_news_last_checksum;
gboolean disable_tip;
} RemminaPref;

View File

@ -247,7 +247,9 @@ void remmina_pref_on_dialog_destroy(GtkWidget *widget, gpointer user_data)
remmina_pref.always_show_notes = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_show_notes));
remmina_pref.hide_connection_toolbar = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_hide_toolbar));
remmina_pref.hide_searchbar = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_hide_searchbar));
remmina_pref.disable_news = gtk_switch_get_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_news));
remmina_pref.disable_stats = gtk_switch_get_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_stats));
remmina_pref.disable_tip = gtk_switch_get_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_tip));
remmina_pref.default_action = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_options_double_click);
remmina_pref.default_mode = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_view_mode);
remmina_pref.tab_mode = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_tab_interface);
@ -498,6 +500,25 @@ static void remmina_pref_dialog_init(void)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_hide_toolbar), remmina_pref.hide_connection_toolbar);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_hide_searchbar), remmina_pref.hide_searchbar);
gtk_switch_set_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_news), remmina_pref.disable_news);
#ifdef DISABLE_NEWS
gtk_widget_hide(GTK_WIDGET(remmina_pref_dialog->switch_disable_news));
gtk_widget_hide(GTK_WIDGET(remmina_pref_dialog->label_disable_news));
#endif
gtk_switch_set_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_stats), remmina_pref.disable_stats);
#ifdef DISABLE_STATS
gtk_widget_hide(GTK_WIDGET(remmina_pref_dialog->switch_disable_stats));
gtk_widget_hide(GTK_WIDGET(remmina_pref_dialog->label_disable_stats));
gtk_switch_set_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_stats), TRUE);
#endif
#ifdef DISABLE_TIP
gtk_widget_hide(GTK_WIDGET(remmina_pref_dialog->switch_disable_tip));
gtk_widget_hide(GTK_WIDGET(remmina_pref_dialog->label_disable_tip));
#endif
gtk_switch_set_active(GTK_SWITCH(remmina_pref_dialog->switch_disable_tip), remmina_pref.disable_tip);
g_snprintf(buf, sizeof(buf), "%i", remmina_pref.sshtunnel_port);
gtk_entry_set_text(remmina_pref_dialog->entry_options_ssh_port, buf);
g_snprintf(buf, sizeof(buf), "%i", remmina_pref.ssh_tcp_keepidle);
@ -728,6 +749,14 @@ GtkWidget *remmina_pref_dialog_new(gint default_tab, GtkWindow *parent)
remmina_pref_dialog->checkbutton_appearance_show_notes = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_appearance_show_notes"));
remmina_pref_dialog->checkbutton_appearance_hide_toolbar = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_appearance_hide_toolbar"));
remmina_pref_dialog->checkbutton_appearance_hide_searchbar = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_appearance_hide_searchbar"));
remmina_pref_dialog->switch_disable_news = GTK_SWITCH(GET_OBJECT("switch_disable_news"));
remmina_pref_dialog->switch_disable_stats = GTK_SWITCH(GET_OBJECT("switch_disable_stats"));
remmina_pref_dialog->switch_disable_tip = GTK_SWITCH(GET_OBJECT("switch_disable_tip"));
remmina_pref_dialog->label_disable_news = GTK_LABEL(GET_OBJECT("remmina_info_disable_news_label"));
remmina_pref_dialog->label_disable_stats = GTK_LABEL(GET_OBJECT("remmina_info_disable_stats_label"));
remmina_pref_dialog->label_disable_tip = GTK_LABEL(GET_OBJECT("remmina_info_disable_tip"));
remmina_pref_dialog->comboboxtext_options_double_click = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_options_double_click"));
remmina_pref_dialog->comboboxtext_appearance_view_mode = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_view_mode"));
remmina_pref_dialog->comboboxtext_appearance_tab_interface = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_tab_interface"));

View File

@ -65,6 +65,12 @@ typedef struct _RemminaPrefDialog {
GtkSwitch * switch_security_lock_view_passwords;
GtkSwitch * switch_security_audit;
GtkSwitch * switch_security_trust_all;
GtkSwitch * switch_disable_news;
GtkSwitch * switch_disable_stats;
GtkSwitch * switch_disable_tip;
GtkLabel * label_disable_news;
GtkLabel * label_disable_stats;
GtkLabel * label_disable_tip;
GtkCheckButton * checkbutton_options_save_settings;
GtkCheckButton * checkbutton_appearance_fullscreen_on_auto;
GtkCheckButton * checkbutton_appearance_show_tabs;

View File

@ -44,15 +44,21 @@
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <openssl/evp.h>
#include <openssl/decoder.h>
#include <openssl/core_names.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include "remmina_sodium.h"
#include "remmina/remmina_trace_calls.h"
#include "remmina_utils.h"
#include "remmina_log.h"
#include "remmina_plugin_manager.h"
/** Returns @c TRUE if @a ptr is @c NULL or @c *ptr is @c FALSE. */
#define EMPTY(ptr) \
(!(ptr) || !*(ptr))
struct utsname u;
/* Copyright (C) 1998 VMware, Inc. All rights reserved.
* Some of the code in this file is taken from the VMware open client.
*/
@ -120,6 +126,40 @@ static DistroInfo distroArray[] = {
{ NULL, NULL },
};
const char *remmina_RSA_PubKey_v1 =
"-----BEGIN PUBLIC KEY-----\n"
"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgaHpEQgAzIDWO2If1MVa\n"
"Qm+vyl2UNdwxFpMBEhC81rdtTqoJBHsMWINheE91AiupXQib9viLNLyu3GhXVd2G\n"
"rGu9QHjw2+UUe35Z8jjUIbSx42PXKxFB49FhuZJ1iBLuP4qLKJoOUKEWrHaP9cYa\n"
"w4G5NN7/f+HVdc7zhWbj4ffeBzblHyxoDSXXEuaBD3xc2MKngn8ZmLi6oeRpEk7v\n"
"L0TdURLRkBrLdYiMxXF8SJpOyLs4wl6w6X6hxJsN2Mtme8XSX1+jK+2uWZKZ3s5K\n"
"tEiHrWUGHbYofv/8cGmgPTCSX+6Ou++lshYjwZYo64of9hK8ttdUnydK5iNFBlWL\n"
"C6YdNSyUNGbtR/CoIYL0dOREpflR5w5801xuNN8XifO1dTOe9glejKdUnWF3hbaR\n"
"XKCgk9UqhlZC1fP5FNcth62OoZdpvHIfPkuTI6add/+sVHeANbza1MFwgNS5rEh0\n"
"U4Gf27Ki0IvWmw8AKHY+0R0e1BCfyClIKjA4pkWSIn9q0YQkHkAze7W5enTA+rND\n"
"yYZuJ72rWmS6Yz05J+I9YlqBWOq07g64YSH+RQWSRRIE47lK9+NXgCGYmwpLsEZo\n"
"Tu4rXKPJ2yyQGYFcVWtWdXfre2VPhoovuQzytSCd2Q57lFXsyw713z/6OCQz9zGb\n"
"eOM74qMLE+jkL1n7snbX5eECAwEAAQ==\n"
"-----END PUBLIC KEY-----\n";
const char *remmina_RSA_PubKey_v2 =
"-----BEGIN PUBLIC KEY-----\n"
"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgaHpEQgAzIDWO2If1MVa\n"
"Qm+vyl2UNdwxFpMBEhC81rdtTqoJBHsMWINheE91AiupXQib9viLNLyu3GhXVd2G\n"
"rGu9QHjw2+UUe35Z8jjUIbSx42PXKxFB49FhuZJ1iBLuP4qLKJoOUKEWrHaP9cYa\n"
"w4G5NN7/f+HVdc7zhWbj4ffeBzblHyxoDSXXEuaBD3xc2MKngn8ZmLi6oeRpEk7v\n"
"L0TdURLRkBrLdYiMxXF8SJpOyLs4wl6w6X6hxJsN2Mtme8XSX1+jK+2uWZKZ3s5K\n"
"tEiHrWUGHbYofv/8cGmgPTCSX+6Ou++lshYjwZYo64of9hK8ttdUnydK5iNFBlWL\n"
"C6YdNSyUNGbtR/CoIYL0dOREpflR5w5801xuNN8XifO1dTOe9glejKdUnWF3hbaR\n"
"XKCgk9UqhlZC1fP5FNcth62OoZdpvHIfPkuTI6add/+sVHeANbza1MFwgNS5rEh0\n"
"U4Gf27Ki0IvWmw8AKHY+0R0e1BCfyClIKjA4pkWSIn9q0YQkHkAze7W5enTA+rND\n"
"yYZuJ72rWmS6Yz05J+I9YlqBWOq07g64YSH+RQWSRRIE47lK9+NXgCGYmwpLsEZo\n"
"Tu4rXKPJ2yyQGYFcVWtWdXfre2VPhoovuQzytSCd2Q57lFXsyw713z/6OCQz9zGb\n"
"eOM74qMLE+jkL1n7snbX5eECAwEAAQ==\n"
"-----END PUBLIC KEY-----\n";
gint remmina_utils_strpos(const gchar *haystack, const gchar *needle)
{
TRACE_CALL(__func__);
@ -257,26 +297,21 @@ static gchar *remmina_utils_read_distrofile(gchar *filename)
GError *err = NULL;
if (g_stat(filename, &st) == -1) {
g_debug("%s: could not stat the file %s\n", __func__, filename);
return NULL;
}
g_debug("%s: File %s is %lu bytes long\n", __func__, filename, st.st_size);
if (st.st_size > 131072)
return NULL;
if (!g_file_get_contents(filename, &distro_desc, &file_sz, &err)) {
g_debug("%s: could not get the file content%s: %s\n", __func__, filename, err->message);
g_error_free(err);
return NULL;
}
if (file_sz == 0) {
g_debug("%s: Cannot work with empty file.\n", __FUNCTION__);
return NULL;
}
g_debug("%s: Distro description %s\n", __func__, distro_desc);
return distro_desc;
}
@ -286,6 +321,7 @@ static gchar *remmina_utils_read_distrofile(gchar *filename)
*/
gchar *remmina_utils_get_lang()
{
TRACE_CALL(__func__);
gchar *lang = setlocale(LC_ALL, NULL);
gchar *ptr;
@ -299,34 +335,50 @@ gchar *remmina_utils_get_lang()
return lang;
}
/**
* Return the OS name as in "uname -s".
* @return The OS name or NULL.
*/
const gchar *remmina_utils_get_kernel_name()
gchar *remmina_utils_get_kernel_name()
{
TRACE_CALL(__func__);
return u.sysname;
struct utsname u;
if (uname(&u) == -1) {
return NULL;
}
return g_strdup(u.sysname);
}
const gchar *remmina_utils_get_kernel_release()
/**
* Return the OS version as in "uname -r".
* @return The OS release or NULL.
*/
gchar *remmina_utils_get_kernel_release()
{
TRACE_CALL(__func__);
return u.release;
struct utsname u;
if (uname(&u) == -1) {
return NULL;
}
return g_strdup(u.release);
}
/**
* Return the machine hardware name as in "uname -m".
* @return The machine hardware name or NULL.
*/
const gchar *remmina_utils_get_kernel_arch()
gchar *remmina_utils_get_kernel_arch()
{
TRACE_CALL(__func__);
return u.machine;
struct utsname u;
if (uname(&u) == -1) {
return NULL;
}
return g_strdup(u.machine);
}
/**
@ -350,15 +402,8 @@ gchar *remmina_utils_get_lsb_description()
{
TRACE_CALL(__func__);
gchar *lsb_description = NULL;
GError *err = NULL;
if (g_spawn_command_line_sync("/usr/bin/lsb_release -sd", &lsb_description, NULL, NULL, &err)) {
if (g_spawn_command_line_sync("/usr/bin/lsb_release -sd", &lsb_description, NULL, NULL, NULL))
return lsb_description;
} else {
g_debug("%s: could not execute lsb_release %s\n", __func__, err->message);
g_error_free(err);
}
g_debug("%s: lsb_release %s\n", __func__, lsb_description);
return NULL;
}
@ -403,11 +448,9 @@ GHashTable *remmina_utils_get_etc_release()
r = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
for (i = 0; distroArray[i].filename != NULL; i++) {
g_debug("%s: File %s\n", __func__, distroArray[i].filename);
etc_release = remmina_utils_read_distrofile(distroArray[i].filename);
if (etc_release) {
if (etc_release[0] != '\0') {
g_debug("%s: Distro description %s\n", __func__, etc_release);
g_hash_table_insert(r, distroArray[i].filename, etc_release);
} else {
g_free(etc_release);
@ -418,30 +461,160 @@ GHashTable *remmina_utils_get_etc_release()
}
/**
* A sample function to show how use the other fOS related functions.
* @return a semicolon separated OS data like in "uname -srm".
* Print device associated with default route.
* @return a string or NULL. Caller must free it with g_free().
*/
const gchar *remmina_utils_get_os_info()
gchar *remmina_utils_get_dev()
{
TRACE_CALL(__func__);
gchar *kernel_string;
gchar *dif;
gint pos = 0;
GString *dev;
if (uname(&u) == -1)
g_print("uname:");
kernel_string = g_strdup_printf("%s;%s;%s\n",
remmina_utils_get_kernel_name(),
remmina_utils_get_kernel_release(),
remmina_utils_get_kernel_arch());
if (!kernel_string || kernel_string[0] == '\0') {
if (kernel_string)
g_free(kernel_string);
kernel_string = g_strdup_printf("%s;%s;%s\n",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN");
if (g_spawn_command_line_sync("ip route show default", &dif, NULL, NULL, NULL)) {
dev = g_string_new(dif);
g_free(dif);
pos = remmina_utils_string_find(dev, pos, -1, "dev ");
dev = g_string_erase(dev, 0, pos + 4);
pos = remmina_utils_string_find(dev, 0, -1, " ");
dev = g_string_truncate(dev, pos);
return g_string_free(dev, FALSE);
}
return kernel_string;
return NULL;
}
/**
* Print address associated with default route.
* @return a string or NULL. Caller must free it with g_free().
*/
gchar *remmina_utils_get_logical()
{
TRACE_CALL(__func__);
gchar *dev = NULL;
gchar *dlog;
gint pos = 0;
dev = remmina_utils_get_dev();
GString *lbuf = g_string_new("ip -4 -o addr show ");
if ( dev != NULL ) {
lbuf = g_string_append(lbuf, dev);
g_free(dev);
}
if (g_spawn_command_line_sync(lbuf->str, &dlog, NULL, NULL, NULL)) {
g_string_free(lbuf, TRUE);
GString *log = g_string_new(dlog);
g_free(dlog);
pos = remmina_utils_string_find(log, pos, -1, "inet ");
log = g_string_erase(log, 0, pos + 5);
pos = remmina_utils_string_find(log, 0, -1, " ");
log = g_string_truncate(log, pos);
return g_string_free(log, FALSE);
}
g_string_free(lbuf, TRUE);
return NULL;
}
/**
* Print link associated with default route.
* @return a string or NULL. Caller must free it with g_free().
*/
gchar *remmina_utils_get_link()
{
TRACE_CALL(__func__);
gchar *dev = NULL;
gchar *plink;
gint pos = 0;
dev = remmina_utils_get_dev();
GString *pbuf = g_string_new("ip -4 -o link show ");
if ( dev != NULL ) {
pbuf = g_string_append(pbuf, dev);
g_free(dev);
}
if (g_spawn_command_line_sync(pbuf->str, &plink, NULL, NULL, NULL))
{
g_string_free(pbuf, TRUE);
GString *link = g_string_new(plink);
g_free(plink);
pos = remmina_utils_string_find(link, pos, -1, "link/ether ");
link = g_string_erase(link, 0, pos + 11);
pos = remmina_utils_string_find(link, 0, -1, " ");
link = g_string_truncate(link, pos);
return g_string_free(link, FALSE);
}
g_string_free(pbuf, TRUE);
return NULL;
}
/**
* Print python version.
* @return a string or NULL. Caller must free it with g_free().
*/
gchar *remmina_utils_get_python()
{
TRACE_CALL(__func__);
gchar *version;
version = (g_spawn_command_line_sync("python -V", &version, NULL, NULL, NULL)) ||
(g_spawn_command_line_sync("python3 -V", &version, NULL, NULL, NULL)) ? version : NULL;
if (version != NULL)
version = remmina_utils_string_strip(version);
return version;
}
/**
* Print machine age.
* @return a string or NULL. Caller must free it with g_free().
*/
gchar *remmina_utils_get_mage()
{
TRACE_CALL(__func__);
gchar *mage = malloc(21);
struct stat sb;
if (stat("/etc/machine-id", &sb) == 0) {
sprintf(mage, "%ld", (long)(time(NULL) - sb.st_mtim.tv_sec));
}
else {
strcpy(mage, "0");
}
return mage;
}
/**
* Create a hexadecimal string version of the SHA-256 digest of the
* buffer.
*
* @return a newly allocated string which the caller should free() when
* finished.
*
* If any error occurs, this function returns NULL.
*/
gchar *remmina_sha256_buffer(const guchar *buffer, gssize length)
{
GChecksum *sha256 = NULL;
gchar *digest_string = NULL;
sha256 = g_checksum_new(G_CHECKSUM_SHA256);
if (sha256 == NULL) {
goto DONE;
}
g_checksum_update(sha256, buffer, length);
digest_string = g_strdup(g_checksum_get_string(sha256));
DONE:
if (sha256) {
g_checksum_free(sha256);
}
return digest_string;
}
/**
@ -496,4 +669,424 @@ DONE:
fclose(file);
return digest;
}
}
/**
* Generate a random sting of chars to be used as part of UID for news or stats
* @return a string or NULL. Caller must free it with g_free().
*/
gchar *remmina_gen_random_uuid()
{
TRACE_CALL(__func__);
gchar *result;
int i;
static char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
result = g_malloc0(15);
for (i = 0; i < 7; i++)
result[i] = alpha[randombytes_uniform(sizeof(alpha) - 1)];
for (i = 0; i < 7; i++)
result[i + 7] = alpha[randombytes_uniform(sizeof(alpha) - 1)];
return result;
}
/**
* Uses OSSL_DECODER_CTX to convert public key string to usable OpenSSL structs
* @return an EVP_PKEY that can be used for public encryption
*/
EVP_PKEY *remmina_get_pubkey(const char *keytype, const char *public_key_selection) {
BIO *pkbio;
OSSL_DECODER_CTX *dctx;
EVP_PKEY *pubkey = NULL;
const char *format = "PEM";
const char *structure = NULL;
dctx = OSSL_DECODER_CTX_new_for_pkey(&pubkey, format, structure,
keytype,
EVP_PKEY_PUBLIC_KEY,
NULL, NULL);
if (dctx == NULL) {
return NULL;
}
pkbio = BIO_new_mem_buf(public_key_selection, -1);
if (OSSL_DECODER_from_bio(dctx, pkbio) == 0) {
BIO_free(pkbio);
OSSL_DECODER_CTX_free(dctx);
return NULL;
}
BIO_free(pkbio);
OSSL_DECODER_CTX_free(dctx);
return pubkey;
}
/**
* Calls EVP_PKEY_encrypt multiple times to encrypt instr. At the end, base64
* encode the resulting buffer.
*
* @param pubkey an RSA public key
* @param instr input string to be encrypted
*
* @return a buffer ptr. Use g_free() to deallocate it
*/
gchar *remmina_rsa_encrypt_string(EVP_PKEY *pubkey, const char *instr)
{
#define RSA_OAEP_PAD_SIZE 42
gchar *enc;
int remaining;
size_t blksz, maxblksz;
unsigned char *outptr;
unsigned char *ebuf = NULL;
gsize ebufLen;
EVP_PKEY_CTX *ctx = NULL;
int pkeyLen = EVP_PKEY_size(pubkey);
int inLen = strlen(instr);
remaining = inLen;
maxblksz = pkeyLen - RSA_OAEP_PAD_SIZE;
ebufLen = (((inLen - 1) / maxblksz) + 1) * pkeyLen;
ebuf = g_malloc(ebufLen);
outptr = ebuf;
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pubkey, NULL);
if (ctx == NULL) {
g_free(ebuf);
return NULL;
}
if (EVP_PKEY_encrypt_init_ex(ctx, NULL) <= 0) {;
EVP_PKEY_CTX_free(ctx);
g_free(ebuf);
return NULL;
}
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
EVP_PKEY_CTX_free(ctx);
g_free(ebuf);
return NULL;
}
/* encrypt input string block by block */
while (remaining > 0) {
blksz = remaining > maxblksz ? maxblksz : remaining;
size_t out_blksz;
int calc_size_return_val = EVP_PKEY_encrypt(ctx, NULL, &out_blksz, (const unsigned char *)instr, blksz);
if (calc_size_return_val == -2) {
EVP_PKEY_CTX_free(ctx);
g_free(ebuf);
return NULL;
}
else if (calc_size_return_val <= 0) {
EVP_PKEY_CTX_free(ctx);
g_free(ebuf);
return NULL;
}
if (EVP_PKEY_encrypt(ctx, outptr, &out_blksz, (const unsigned char *)instr, blksz) <= 0) {;
EVP_PKEY_CTX_free(ctx);
g_free(ebuf);
return NULL;
}
instr += blksz;
remaining -= blksz;
outptr += out_blksz;
}
enc = g_base64_encode(ebuf, ebufLen);
g_free(ebuf);
EVP_PKEY_CTX_free(ctx);
return enc;
}
/**
* Attempts to verify the contents of @a plugin_file with base64 encoded @a signature. This function uses the OpenSSL EVP API
* and expects a well-formed EC signature in DER format. The digest function used is SHA256.
*
* @param signature A base64 encoded signature.
* @param plugin_file The file containing the message you wish to verify.
* @param message_length The length of the message.
* @param signature_length The length of the signature.
*
* @return TRUE if the signature can be verified or FALSE if it cannot or there is an error.
*/
gboolean remmina_verify_plugin_signature(const guchar *signature, GFile* plugin_file, size_t message_length,
size_t signature_length) {
EVP_PKEY *public_key = NULL;
/* Get public key */
public_key = remmina_get_pubkey(RSA_KEYTYPE, remmina_RSA_PubKey_v2);
if (public_key == NULL) {
return FALSE;
}
gboolean verified = remmina_execute_plugin_signature_verification(plugin_file, message_length, signature,
signature_length, public_key);
EVP_PKEY_free(public_key);
if (verified) {
return TRUE;
} else {
return FALSE;
}
}
/**
* A helper function that performs the actual signature verification (that is, this function makes direct calls to the
* OPENSSL EVP API) of @a plugin_file using signature @a sig with @a public_key.
*
* @param plugin_file The file containing the message you wish to verify.
* @param msg_len The length of the message.
* @param sig The signature to verify the message with.
* @param sig_len The length of the signature.
* @param public_key The public key to use in the signature verification.
*
* @return TRUE if the signature can be verified or FALSE if there is an error or the signature cannot be verified.
*/
gboolean remmina_execute_plugin_signature_verification(GFile* plugin_file, size_t msg_len, const guchar *sig, size_t sig_len, EVP_PKEY* public_key)
{
/* Returned to caller */
if(!plugin_file || !msg_len || !sig || !sig_len || !public_key) {
return FALSE;
}
EVP_MD_CTX* ctx = NULL;
gssize read = 0;
gssize total_read = 0;
unsigned char buffer[1025];
/* Create context for verification */
ctx = EVP_MD_CTX_create();
if(ctx == NULL) {
return FALSE;
}
/* Returns the message digest (hash function) specified*/
const EVP_MD* message_digest = EVP_get_digestbyname(NAME_OF_DIGEST);
if(message_digest == NULL) {
EVP_MD_CTX_free(ctx);
return FALSE;
}
/* Set up context with the corresponding message digest. 1 is the only code for success */
int return_val = EVP_DigestInit_ex(ctx, message_digest, NULL);
if(return_val != 1) {
EVP_MD_CTX_free(ctx);
return FALSE;
}
/* Add the public key and initialize the context for verification*/
return_val = EVP_DigestVerifyInit(ctx, NULL, message_digest, NULL, public_key);
if(return_val != 1) {
EVP_MD_CTX_free(ctx);
return FALSE;
}
GFileInputStream *in_stream = g_file_read(plugin_file, NULL, NULL);
/* Add the msg to the context. The next step will be to actually verify the added message */
read = g_input_stream_read(G_INPUT_STREAM(in_stream), buffer, 1024, NULL, NULL);
while (read > 0){
total_read += read;
return_val = EVP_DigestVerifyUpdate(ctx, buffer, read);
if(return_val != 1) {
EVP_MD_CTX_free(ctx);
return FALSE;
}
read = g_input_stream_read(G_INPUT_STREAM(in_stream), buffer, 1024, NULL, NULL);
}
msg_len = total_read;
/* Clear any errors for the call below */
ERR_clear_error();
/* Execute the signature verification. */
return_val = EVP_DigestVerifyFinal(ctx, sig, sig_len);
if(return_val != 1) {
EVP_MD_CTX_free(ctx);
return FALSE;
}
if(ctx != NULL) {
EVP_MD_CTX_destroy(ctx);
ctx = NULL;
}
return TRUE;
}
/**
*
* Decompresses pointer @source with length @len to file pointed to by @plugin_file. It is the caller's responsibility
* to free @source and @plugin_file when no longer in use.
*
* @return The total number of compressed bytes read or -1 on error.
*/
int remmina_decompress_from_memory_to_file(guchar *source, int len, GFile* plugin_file) {
GError *error = NULL;
//Compress with gzip. The level is -1 for default
GZlibDecompressor* gzlib_decompressor = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
if (gzlib_decompressor == NULL) {
return -1;
}
GInputStream *base_stream = g_memory_input_stream_new_from_data(source, len, NULL);
if (base_stream == NULL) {
g_object_unref(gzlib_decompressor);
return -1;
}
GInputStream *converted_input_stream = g_converter_input_stream_new(base_stream, G_CONVERTER(gzlib_decompressor));
if (converted_input_stream == NULL) {
g_object_unref(base_stream);
g_object_unref(gzlib_decompressor);
return -1;
}
int total_read = 0;
GFileOutputStream *f_out_stream = g_file_replace(plugin_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
if (f_out_stream == NULL){
g_object_unref(base_stream);
g_object_unref(gzlib_decompressor);
return -1;
}
g_output_stream_splice(f_out_stream, converted_input_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, NULL);
GFileInfo* info = g_file_query_info(plugin_file, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
total_read = g_file_info_get_size(info);
//Freeing stuff
g_object_unref(converted_input_stream);
g_object_unref(f_out_stream);
g_object_unref(gzlib_decompressor);
g_object_unref(base_stream);
g_object_unref(info);
return total_read;
}
/**
* Compresses file @source to file @dest. Creates enough space to compress MAX_COMPRESSED_FILE_SIZE. It is the caller's
* responsibility to close @source and @dest.
*
* @return Total number of bytes read from source or -1 on error.
*/
int remmina_compress_from_file_to_file(GFile *source, GFile *dest)
{
TRACE_CALL(__func__);
GError *error = NULL;
// Compress with gzip. The level is -1 for default
GZlibCompressor *gzlib_compressor = g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
if (gzlib_compressor == NULL) {
REMMINA_DEBUG("Compressor instantiation failed");
return -1;
}
GFileInputStream *base_stream = g_file_read(source, NULL, &error);
if (base_stream == NULL) {
REMMINA_DEBUG("Base stream is null, error: %s", error->message);
g_free(error);
g_object_unref(gzlib_compressor);
return -1;
}
GInputStream *converted_input_stream = g_converter_input_stream_new((GInputStream *) base_stream, G_CONVERTER(gzlib_compressor));
if (converted_input_stream == NULL) {
REMMINA_DEBUG("Failed to allocate converted input stream");
g_object_unref(base_stream);
g_object_unref(gzlib_compressor);
return -1;
}
int total_read = 0;
guint8 *converted, *current_position;
converted = g_malloc(sizeof(guint8) * MAX_COMPRESSED_FILE_SIZE); // 100kb file max
current_position = converted; // Set traveling pointer
gsize bytes_read = 0;
while (total_read < MAX_COMPRESSED_FILE_SIZE) {
bytes_read = g_input_stream_read(G_INPUT_STREAM(converted_input_stream), current_position, 1, NULL, &error);
if (error != NULL) {
REMMINA_DEBUG("Error reading input stream: %s", error->message);
g_free(error);
g_object_unref(base_stream);
g_object_unref(gzlib_compressor);
g_object_unref(converted_input_stream);
g_free(converted);
return -1;
}
if (bytes_read == 0) {
break; // Reached EOF, break read
}
current_position += bytes_read;
total_read += bytes_read;
}
if (total_read >= MAX_COMPRESSED_FILE_SIZE) {
REMMINA_DEBUG("Detected heap overflow in allocating compressed file");
g_object_unref(base_stream);
g_object_unref(gzlib_compressor);
g_object_unref(converted_input_stream);
g_free(converted);
}
gsize *bytes_written = g_malloc(sizeof(gsize) * total_read);
GFileOutputStream *g_output_stream = g_file_append_to(dest, G_FILE_CREATE_NONE, NULL, &error);
if (g_output_stream == NULL) {
REMMINA_DEBUG("Error appending to file: %s", error->message);
g_error_free(error);
g_object_unref(base_stream);
g_object_unref(gzlib_compressor);
g_object_unref(converted_input_stream);
g_free(bytes_written);
g_free(converted);
return -1;
}
g_output_stream_write_all(g_output_stream, converted, total_read, bytes_written, NULL, &error);
if (error != NULL) {
REMMINA_DEBUG("Error writing all bytes to file: %s", error->message);
g_error_free(error);
g_object_unref(base_stream);
g_object_unref(gzlib_compressor);
g_object_unref(converted_input_stream);
g_object_unref(g_output_stream);
g_free(bytes_written);
g_free(converted);
return -1;
}
g_object_unref(base_stream);
g_object_unref(gzlib_compressor);
g_object_unref(converted_input_stream);
g_object_unref(g_output_stream);
g_free(bytes_written);
g_free(converted);
return total_read;
}

View File

@ -40,22 +40,46 @@
#pragma once
#include <glib.h>
#include <gio/gio.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/decoder.h>
#define MAX_COMPRESSED_FILE_SIZE 100000 //100kb file size max
#define NAME_OF_DIGEST "SHA256"
#define RSA_KEYTYPE "RSA"
extern const char *remmina_RSA_PubKey_v1;
extern const char *remmina_RSA_PubKey_v2;
extern const char *remmina_EC_PubKey;
G_BEGIN_DECLS
gint remmina_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle);
gint remmina_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace);
guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace);
gchar *remmina_utils_string_strip(const gchar *s);
gchar *remmina_utils_get_lang();
const gchar *remmina_utils_get_kernel_name();
const gchar *remmina_utils_get_kernel_release();
const gchar *remmina_utils_get_kernel_arch();
gchar *remmina_utils_get_kernel_name();
gchar *remmina_utils_get_kernel_release();
gchar *remmina_utils_get_kernel_arch();
gchar *remmina_utils_get_lsb_id();
gchar *remmina_utils_get_lsb_description();
gchar *remmina_utils_get_lsb_release();
gchar *remmina_utils_get_lsb_codename();
GHashTable *remmina_utils_get_etc_release();
const gchar *remmina_utils_get_os_info();
gchar *remmina_utils_get_dev();
gchar *remmina_utils_get_logical();
gchar *remmina_utils_get_link();
gchar *remmina_utils_get_python();
gchar *remmina_utils_get_mage();
gchar *remmina_sha256_buffer(const guchar *buffer, gssize length);
gchar *remmina_sha1_file(const gchar *filename);
gchar *remmina_gen_random_uuid();
EVP_PKEY *remmina_get_pubkey(const char *keytype, const char *public_key_selection);
gchar *remmina_rsa_encrypt_string(EVP_PKEY *pubkey, const char *instr);
int remmina_decompress_from_memory_to_file(guchar *source, int len, GFile* plugin_file);
int remmina_compress_from_file_to_file(GFile *source, GFile *dest);
gboolean remmina_verify_plugin_signature(const guchar *signature, GFile* plugin_file, size_t message_length, size_t signature_length);
gboolean remmina_execute_plugin_signature_verification(GFile* plugin_file, size_t msg_len, const guchar *sig, size_t sig_len, EVP_PKEY* public_key);
G_END_DECLS