Netgate Discussion Forum
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Search
    • Register
    • Login

    Email Notification - OpenVPN Client Connect (Common Name)

    Scheduled Pinned Locked Moved OpenVPN
    138 Posts 20 Posters 39.2k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • viktor_gV
      viktor_g Netgate
      last edited by

      You can create a feature request for this:
      https://docs.netgate.com/pfsense/en/latest/development/feature-requests.html

      noplanN 1 Reply Last reply Reply Quote 0
      • noplanN
        noplan @viktor_g
        last edited by

        @viktor_g said in Email Notification - OpenVPN Client Connect (Common Name):

        You can create a feature request for this:

        for what ?
        the eMail notification ?

        yes notification would be cool, but cooler but not job of the firewall
        would be a reporting tool with output like in post#76

        anyhow reading this from top to bottom
        usin search function of this forum there will pop up a pretty professional tool
        based on this a lot of people are using to get information about their openVPN connections
        especially when you have to monitor contractors and other folks gettin access to your network

        or am i gettin it wrong ?

        br NP

        1 Reply Last reply Reply Quote 0
        • S
          Stewart @noplan
          last edited by Stewart

          @noplan

          I wanted to update on what I've created so far. I hope this all comes through properly. There is still experimental stuff in there as I figure it out and the code is sloppy in some places (especially as I hacked in the stuff to make it blend in to the pfSense theming) but the basic stuff is all there.

          connect.sh

          #!/usr/local/bin/php -q
          
          <?php
          //      Gather Info
                  $date = date('F j, Y');
                  $time = date('g:i a');
                  require_once("/etc/inc/notices.inc");
          
          //      Compile Info String to Send
                  $local_connect_value = "USER: " . getenv('common_name') . "; CONNECTED; CONNECTED ON: " . $date . "; at " .$time . "; IP CONNECTED FROM: " . getenv('trusted_ip') . " INTERNAL IP ASSIGNED: " . getenv('ifconfig_pool_remote_ip') . "\n";
          
          //      Send Email Notification of Event
          //      notify_all_remote($local_connect_value);
          
          $filename="/var/log/OpenVPN_Users.log";
          $fp = fopen($filename, 'a');
          fwrite($fp,$local_connect_value);
          fclose($fp);
          
          ?>
          
          

          disconnect.sh

          #!/usr/local/bin/php -q
          <?php
          //      Gather Info
                  require_once("/etc/inc/notices.inc");
          //      $date = date('F j, Y, g:i a');
                  $date = date('F j, Y');
                  $time = date('g:i a');
                  $durationSeconds = getenv('time_duration');
          
          //      Function to calculate time from seconds to human readable format
                  function seconds2human($ss) {
                          $s = $ss%60;
                          $m = floor(($ss%3600)/60);
                          $h = floor(($ss%86400)/3600);
                          $d = floor(($ss%2592000)/86400);
                          $M = floor($ss/2592000);
                          return "$d days $h hours $m minutes $s seconds";
                  }
          
          //      Compile Info String to Send
                  $local_connect_value .= "USER: " . getenv('common_name') . "; DISCONNECTED; DISCONNECTED ON: " . $date . "; at " . $time . "; DURATION: " . $durationSeconds . " seconds or " . seconds2human($durationSeconds) . " DATA UPLOADED (RECEIVED): " . round(((getenv('bytes_received'))/1048576),2) . " MB DATA DOWNLOADED (SENT): " . round(((getenv('bytes_sent'))/1048576),2) ." MB\n";
          
          //      Send Email Notification of Event
          //      notify_all_remote($local_connect_value);
          
          
          //      Output to Log File
                  $filename="/var/log/OpenVPN_Users.log";
                  $fp = fopen($filename, 'a');
                  fwrite($fp,$local_connect_value);
                  fclose($fp);
          
          ?>
          
          
          

          OpenVPNUsers.log

          USER: LTest; CONNECTED; CONNECTED ON: September 15, 2021; at 3:32 pm; IP CONNECTED FROM: 111.111.111.111 INTERNAL IP ASSIGNED: 10.0.100.2
          USER: LTest; DISCONNECTED; DISCONNECTED ON: September 15, 2021; at 3:32 pm; DURATION: 17 seconds or 0 days 0 hours 0 minutes 17 seconds DATA UPLOADED (RECEIVED): 0.28 MB DATA DOWNLOADED (SENT): 0.17 MB
          
          

          /usr/local/www/vpn_openvpn_userlogging.php

          <?php
          /*
           * vpn_openvpn_userlogging.php
           *
           * Sept 2021
           */
          
          ##|+PRIV
          ##|*IDENT=page-openvpn-userlogging
          ##|*NAME=OpenVPN: User Logging
          ##|*DESCR=View OpenVPN User Connections.
          ##|*MATCH=vpn_openvpn_userlogging.php*
          ##|-PRIV
          
          require_once("guiconfig.inc");
          require_once("openvpn.inc");
          require_once("pfsense-utils.inc");
          require_once("pkg-utils.inc");
          
          global $openvpn_topologies, $openvpn_tls_modes;
          
          $shortcut_section = "openvpn";
          include("head.inc");
          
          
          
          $tab_array = array();
          $tab_array[] = array(gettext("Servers"), false, "vpn_openvpn_server.php");
          $tab_array[] = array(gettext("Clients"), true, "vpn_openvpn_client.php");
          $tab_array[] = array(gettext("Client Specific Overrides"), false, "vpn_openvpn_csc.php");
          $tab_array[] = array(gettext("Wizards"), false, "wizard.php?xml=openvpn_wizard.xml");
          add_package_tabs("OpenVPN", $tab_array);
          display_top_tabs($tab_array);
          
          
          
          // Function to calculate time from seconds to human readable format
          function seconds2human($ss) {
                  $s = $ss%60;
                  $m = floor(($ss%3600)/60);
                  $h = floor(($ss%86400)/3600);
                  $d = floor(($ss%2592000)/86400);
                  $M = floor($ss/2592000);
                  return "$d days $h hours $m minutes $s seconds";
          }
          
          //Variables
          $logFilename = '/var/log/OpenVPN_Users.log';            //Log to be read in to generate information from
          $userFilename = '/tmp/OpenVPN_UniqueUsers.log';         //Scratchpad that stores usernames that then get read back into an array
          $nameSelected = $_POST["user_selected"];                //Receive the user selected and filter based on it
          $secondsCounter = 0;                                    //Running Tally of Time
          $dataUploadedTotal = 0;                                 //Running Tally of Data Uploaded
          $dataDownloadedTotal = 0;                               //Running Tally of Data Downloaded
          
          //Delete the $userFilename log so it can be filled fresh with each run
          unlink($userFilename);
          ?>
          
          
          <body>
          
          
          <div class="panel panel-default">
          
                  <div class="panel-heading"><h2 class="panel-title"><?=gettext('Client Connections')?>
          <?php
                  if ($nameSelected !== "" && $nameSelected !== "Choose one"){
                          echo "<br />".'User Selected='.$nameSelected;
                  }
          ?>
                  </h2></div>
                  <div class="table-responsive">
                  <table id="tblResults" class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
          
                          <thead><tr><th width=8%>USER</th><th width=12%>EVENT</th><th width=13%>EVENT DATE</th><th width=7%>EVENT TIME</th><th width=60%>INFORMATION</th></tr></thead><tbody>
          
                          <?php
                          $dataArray = [];                                              // The nested array to hold all the arrays
                          if (($h = fopen("{$logFilename}", "r")) !== FALSE) {             // Open the file for reading
                                  while (($data = fgetcsv($h, 500, ";")) !== FALSE){
                                          $dataArray[] = $data;
                                          echo '<tr>';
                                          $i=0;
                                          foreach ($data as $column) {
                                                  $i++;
                                                  if (($fh = fopen("{$userFilename}", "a")) !== FALSE) {}             // Open the file for writing
                                                  if ($i==1){
                                                          fwrite($fh,substr(htmlspecialchars($column)."\n",6));       // Write name of connected user
                                                          if ($nameSelected == "" || $nameSelected == "Choose one"){
                                                                  echo '<td>'.substr(htmlspecialchars($column),6).'</td>';
                                                          }else{
                                                                  if (substr(htmlspecialchars($column),6)==$nameSelected) {
                                                                          echo '<td>'.substr(htmlspecialchars($column),6).'</td>';
                                                                  }else{
                                                                          $i=99;
                                                                  }
                                                          }
                                                  }elseif($i==2){
                                                          echo '<td>'.htmlspecialchars($column).'</td>';
                                                  }elseif($i==3){
                                                          if (strpos($column,"DISCONNECTED") !== false){
                                                                  $eventStatus="DISCONNECT";
                                                                  echo '<td>'.substr(htmlspecialchars($column),18).'</td>';
                                                          }else{
                                                                  $eventStatus="CONNECT";
                                                                  echo '<td>'.substr(htmlspecialchars($column),15).'</td>';
                                                          }
                                                  }elseif($i==4){
                                                          echo '<td>'.substr(htmlspecialchars($column),3).'</td>';
                                                  }elseif($i==5){
                                                          if ($eventStatus=="CONNECT") {
                                                                  $needle = "IP";
                                                                  $insertCode = "<br/>";
                                                                  $pos = strpos($column, $needle);
                                                                  $newColumn = substr_replace($column, $insertCode , $pos , 0);
                                                                  $newColumn = str_replace("INTERNAL","<BR/> INTERNAL",$column);
                                                                  echo '<td>'.$newColumn.'</td>';
                                                          }elseif($eventStatus=="DISCONNECT") {
                                                                  //Manipulate Time
                                                                  $seconds=strstr(substr(htmlspecialchars($column),10), "s", true);
                                                                  $secondsCounter += $seconds;
                                                                  //Display Data
                                                                  echo '<td>'.str_replace("DATA","<BR/> DATA",$column).'</td>';
                                                                  //Manipulate Data Uploaded
                                                                  $dataUploaded = strstr(substr(strstr($column, "): ", false),3)," ", true);
                                                                  $dataUploadedTotal += $dataUploaded;
                                                                  //Manipulate Data Downloaded
                                                                  $dataDownloaded = strstr(substr(strstr(substr(strstr($column, "): ", false),3), "): ", false),3)," ", true);
                                                                  $dataDownloadedTotal += $dataDownloaded;
                                                          }
                                                  }
                                          }
                                          echo '</tr>'.PHP_EOL;
                                          fclose($fh);
                                  }
                          }
          
                  echo '<tr><td></td><td></td><td></td><td></td><td>Total Time Connected: <strong>'.seconds2human($secondsCounter) . '</strong>';
                  echo '<br />Total Data Uploaded (Received): <strong>' . $dataUploadedTotal . ' MB</strong>';
                  echo '<br />Total Data Downloaded (Sent): <strong>' . $dataDownloadedTotal . ' MB</strong></td></tr>';
          
          
          
                  echo "</tbody></table>";
          echo "</div>";
          
          
          
          
          
          $arrUserlist = file("$userFilename", FILE_IGNORE_NEW_LINES);
          echo "<form method='post' action='vpn_openvpn_userlogging.php'>";
          echo "<select name='user_selected'><option selected='selected'>Choose one</option>";
          
          foreach(array_unique($arrUserlist) as $item){
                  ?><option value="<?php echo $item; ?>"><?php echo $item; ?></option><?php
          }
          echo "</select><input type='submit' value='Submit'></form>";
          
          ?>
          
          <!--This section isn't used yet.  Can't get it to implement the datatable and still keep the formatting properly. -->
          
          <!-- This loads up the basic jQuery library (version 3.6.0) -->
          <script src="//code.jquery.com/jquery-3.6.0.min.js"></script>
          
          <!-- This loads up the CSS styling for the resulting DataTable (e.g. sorting icons, basic formatting, etc) -->
          <link rel="stylesheet" type="text/css" href="//cdn.datatables.net/1.11.1/css/jquery.dataTables.min.css" />
          
          <!-- This loads up the jQuery plugin that enables the use of the DataTable() function used below -->
          <script src="//cdn.datatables.net/1.11.1/js/jquery.dataTables.min.js"></script>
          
          <!-- This will (when the page loads completely) find the table with the ID of "tblResults" and format it with the DataTables plugin -->
          
          <script>
          // When the page is loaded...
          $(document).ready( function () {
          
              // This one command does all the work
              $('#tblResults').DataTable();
          
          } );
          </script>
          
          </body>
          </html>
          
          
          <?php include("foot.inc");?>
          
          

          What I'd like to do is make the table filterable based on users and date range. I was going to start trying it in php but maybe it's already a built-in class that's been made like how it is already sortable. I've also had Javascript recommended to me. I don't want to reinvent the wheel so let me know if I'm missing something. I'd also like to add in a function to sum up all of the time of the disconnected sessions to see how long someone has been connected. That way administrators can see how long a user was connected over a day, or week, or however long they want to see for. I'd also like to tally up the data usage for visibility.

          Edit: Since nobody has replied since I posted it, I've updated the code for whomever is interested. I guess all that's left is to filter by date range.

          Edit 2: Sample Output
          04ce19a2-a833-4ee1-a876-5ac7f0e82129-image.png

          1 Reply Last reply Reply Quote 0
          • S
            Stewart
            last edited by Stewart

            I don't know how much interest there is in this since I haven't received feedback from the code I've posted, but here is the updated code I have so far.

            connect.sh - Script that gets launched whenever someone connects to the VPN

            #!/usr/local/bin/php -q
            
            <?php
            //      Gather Info
                    $date = date('Y-m-d');
                    $time = date('g:i a');
                    require_once("/etc/inc/notices.inc");
            
            //      Compile Info String to Send
                    $local_connect_value = "USER: " . getenv('common_name') . "; CONNECTED; " . $date . "; at " .$time . "; IP CONNECTED FROM: " . getenv('trusted_ip') . " INTERNAL IP ASSIGNED: " . getenv('ifconfig_pool_remote_ip') . "\n";
            
            //      Send Email Notification of Event
            //      notify_all_remote($local_connect_value);
            
            $filename="/var/log/OpenVPN_Users.log";
            $fp = fopen($filename, 'a');
            fwrite($fp,$local_connect_value);
            fclose($fp);
            
            ?>
            
            

            disconnect.sh - Script that gets launched whenever someone disconnects the OpenVPN

            #!/usr/local/bin/php -q
            <?php
            //      Gather Info
                    require_once("/etc/inc/notices.inc");
                    $date = date('Y-m-d');
                    $time = date('g:i a');
                    $durationSeconds = getenv('time_duration');
            
            //      Function to calculate time from seconds to human readable format
                    function seconds2human($ss) {
                            $s = $ss%60;
                            $m = floor(($ss%3600)/60);
                            $h = floor(($ss%86400)/3600);
                            $d = floor(($ss%2592000)/86400);
                            $M = floor($ss/2592000);
                            return "$d days $h hours $m minutes $s seconds";
                    }
            
            //      Compile Info String to Send
                    $local_connect_value .= "USER: " . getenv('common_name') . "; DISCONNECTED; " . $date . "; at " . $time . "; DURATION: " . $durationSeconds . " seconds or " . seconds2human($durationSeconds) . " DATA UPLOADED (RECEIVED): ". round(((getenv('bytes_received'))/1048576),2) . " MB DATA DOWNLOADED (SENT): " . round(((getenv('bytes_sent'))/1048576),2) ." MB\n";
            
            //      Send Email Notification of Event
            //      notify_all_remote($local_connect_value);
            
            
            //      Output to Log File
                    $filename="/var/log/OpenVPN_Users.log";
                    $fp = fopen($filename, 'a');
                    fwrite($fp,$local_connect_value);
                    fclose($fp);
            
            ?>
            

            /var/log/OpenVPN_Users.log - File that logs the info from the connect and disconnect scripts
            This is sample logs:

            USER: LTest; CONNECTED; 2021-09-15; at 3:32 pm; IP CONNECTED FROM: 111.111.111.111 INTERNAL IP ASSIGNED: 10.0.100.2
            USER: LTest; DISCONNECTED; 2021-09-15; at 3:32 pm; DURATION: 17 seconds or 0 days 0 hours 0 minutes 17 seconds DATA UPLOADED (RECEIVED): 0.28 MB DATA DOWNLOADED (SENT): 0.17 MB
            

            /usr/local/www/vpn_openvpn_userlogging.php - Web Page to view with

            <?php
            /*
             * vpn_openvpn_userlogging.php
             *
             * Sept 2021
             */
            
            ##|+PRIV
            ##|*IDENT=page-openvpn-userlogging
            ##|*NAME=OpenVPN: User Logging
            ##|*DESCR=View OpenVPN User Connections.
            ##|*MATCH=vpn_openvpn_userlogging.php*
            ##|-PRIV
            
            require_once("guiconfig.inc");
            require_once("openvpn.inc");
            require_once("pfsense-utils.inc");
            require_once("pkg-utils.inc");
            
            global $openvpn_topologies, $openvpn_tls_modes;
            
            $shortcut_section = "openvpn";
            include("head.inc");
            
            $tab_array = array();
            $tab_array[] = array(gettext("Servers"), false, "vpn_openvpn_server.php");
            $tab_array[] = array(gettext("Clients"), true, "vpn_openvpn_client.php");
            $tab_array[] = array(gettext("Client Specific Overrides"), false, "vpn_openvpn_csc.php");
            $tab_array[] = array(gettext("Wizards"), false, "wizard.php?xml=openvpn_wizard.xml");
            add_package_tabs("OpenVPN", $tab_array);
            display_top_tabs($tab_array);
            ?>
            
            <head><style>
            /* ************************* This style is used for the popup date picker ************************* */
            /* (A) POPUP */
            .picker-wrap {
              position: fixed;
              top: 0;
              left: 0;
              width: 100vw;
              height: 100vh;
              background: rgba(0,0,0,0.5);
              opacity: 0;
              visibility: hidden;
              transition: opacity 0.2s;
            }
            .picker-wrap.show {
              opacity: 1;
              visibility: visible;
            }
            .picker-wrap .picker {
              margin: 50vh auto 0 auto;
              transform: translateY(-50%);
            }
            
            /* (B) CONTAINER */
            .picker {
              max-width: 300px;
              background: #444444;
              padding: 10px;
            }
            
            /* (C) MONTH + YEAR */
            .picker-m, .picker-y {
              width: 50%;
              padding: 5px;
              box-sizing: border-box;
              font-size: 16px;
            }
            
            /* (D) DAY */
            .picker-d table {
              color: #fff;
              border-collapse: separate;
              width: 100%;
              margin-top: 10px;
            }
            .picker-d table td {
              width: 14.28%; /* 7 EQUAL COLUMNS */
              padding: 5px;
              text-align: center;
            }
            /* HEADER CELLS */
            .picker-d-h td {
              font-weight: bold;
            }
            /* BLANK DATES */
            .picker-d-b {
              background: #4e4e4e;
            }
            /* TODAY */
            .picker-d-td {
              background: #d84f4f;
            }
            /* PICKABLE DATES */
            .picker-d-d:hover {
              cursor: pointer;
              background: #a33c3c;
            }
            /* UNPICKABLE DATES */
            .picker-d-dd {
              color: #888;
              background: #4e4e4e;
            }
            /* ************************* End style used for the popup date picker ************************* */
            </style></head>
            
            
            
            <?php
            // ************************* Start of Functions, Variables, and Pre-Work *************************
            // Function to calculate time from seconds to human readable format
            function seconds2human($ss) {
            	$s = $ss%60;
            	$m = floor(($ss%3600)/60);
            	$h = floor(($ss%86400)/3600);
            	$d = floor(($ss%2592000)/86400);
            	$M = floor($ss/2592000);
            	return "$d days $h hours $m minutes $s seconds";
            }
            
            //Variables
            //$logFilename = '/var/log/OpenVPN_Users.log';		//Log to be read in to generate information from
            //$userFilename = '/tmp/OpenVPN_UniqueUsers.log';		//Scratchpad that stores usernames that then get read back into an array
            //$nameSelected = $_POST["user_selected"];		//Receive the user selected and filter based on it
            //$secondsCounter = 0;					//Running Tally of Time
            //$dataUploadedTotal = 0;					//Running Tally of Data Uploaded
            //$dataDownloadedTotal = 0;				//Running Tally of Data Downloaded
            
            
            //Variables
            $logFilename = '/var/log/OpenVPN_Users.log';            //Log to be read in to generate information from
            $userFilename = '/tmp/OpenVPN_UniqueUsers.log';         //Scratchpad that stores usernames that then get read back into an array
            $nameSelected = $_POST["user_selected"];                //Receive the user selected and filter based on it
            $dateSelected = $_POST["input-pop"];                    //Receive date and filter based on it
            $secondsCounter = 0;                                    //Running Tally of Time
            $dataUploadedTotal = 0;                                 //Running Tally of Data Uploaded
            $dataDownloadedTotal = 0;                               //Running Tally of Data Downloaded
            $passesDateFilter = "";
            $dataArray = [];                                        //Used to store all
            unlink($userFilename);                                  //Delete the $userFilename log so it can be filled fresh with each run
            
            
            //Delete the $userFilename log so it can be filled fresh with each run
            unlink($userFilename);
            // ************************* End of Functions, Variables, and Pre-Work *************************
            ?>
            
            <body><div class='panel panel-default'><div class='panel-heading'><h2 class='panel-title'>
            <?=gettext('Client Connections')?>
            
            <? 
            ob_start();  // ************************* HTML Store and Load Point *************************
            ?>
            
            </h2></div>
            <div class="table-responsive">
            <table id="tblResults" class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
            <thead><tr><th width=8%>USER</th><th width=12%>EVENT</th><th width=13%>EVENT DATE</th><th width=7%>EVENT TIME</th><th width=60%>INFORMATION</th></tr></thead><tbody>
            
            <?php
            // Outputs
            $fh = fopen("{$userFilename}", "a");
            
            // Input
            if (($h = fopen("{$logFilename}", "r")) !== FALSE)
            {
              // Open the file for reading
              while (($data = fgetcsv($h, 500, ";")) !== FALSE)
              {
                // ************************* Assign the Array Information *************************
                // Assign the $data record to variables for more readable code
                $recUser    = $data[0];     // User
                $recEvent   = $data[1];     // Event
                $recDate    = $data[2];     // Event Date
                $recTime    = $data[3];     // Event Time
                $recInfo    = $data[4];     // Event Information
            
                // Write name of connected user to scratch file
                fwrite($fh,substr(htmlspecialchars($recUser)."\n",6));
            
                // ************************* Perform Checks to see if row should be displayed *************************
                // Evaluate if the row passes the filters
                $passesNameFilter = ($nameSelected == "" || $nameSelected == "Select User" || $nameSelected == substr(htmlspecialchars($recUser),6));
                $passesDateFilter = ($dateSelected == "" || $dateSelected == trim(htmlspecialchars($recDate)));
            
                // If either of the filters fails to pass...
                if($passesNameFilter == false || $passesDateFilter == false)
                {
                  // Continue on and exit the while loop if name/date criterea aren't met
                  continue;
                }
            
                // If both filters pass, capture the raw row in $dataArray
                $dataArray[] = $data;
            
                // ************************* For a row to be displayed, prepare the informatin *************************
                // Prepare the data from recdata (received data) to htmldata (data to be used in formatting and parsing)
                $htmlUser = substr(htmlspecialchars($recUser),6);
                $htmlEvent = htmlspecialchars($recEvent);
                $htmlTime = substr(htmlspecialchars($recTime),3);
                $htmlInfo = htmlspecialchars($recInfo);
                $htmlDate = trim(htmlspecialchars($recDate));
            
                // Event Info - Parse the Information Column Depending on Event Type
                if (strpos($recEvent,"DISCONNECTED") !== false){
                  //Manipulate Time
                  $seconds=strstr(substr(htmlspecialchars($recInfo),10), "s", true);
                  $secondsCounter += $seconds;
                  //Display Data
                  $htmlInfo = str_replace("DATA","<BR/> DATA",$recInfo);
                  //Manipulate Data Uploaded
                  $dataUploaded = strstr(substr(strstr($recInfo, "): ", false),3)," ", true);
                  $dataUploadedTotal += $dataUploaded;
                  //Manipulate Data Downloaded
                  $dataDownloaded = strstr(substr(strstr(substr(strstr($recInfo, "): ", false),3), "): ", false),3)," ", true);
                  $dataDownloadedTotal += $dataDownloaded;
                }elseif (strpos($recEvent,"CONNECTED") != false) {
                  $needle = "IP";
                  $insertCode = "<br/>";
                  $pos = strpos($recInfo, $needle);
                  $htmlInfo = substr_replace($recInfo, $insertCode , $pos , 0);
                  $htmlInfo = str_replace("INTERNAL","<BR/> INTERNAL",$recInfo);
                }
            
                // ************************* Display the Information *************************
                // Now render the row
                echo "<tr>";
                echo "<td>{$htmlUser}<br/></td>";
                echo "<td>{$htmlEvent}<br/></td>";
                echo "<td>{$htmlDate}<br/></td>";
                echo "<td>{$htmlTime}<br/></td>";
                echo "<td>{$htmlInfo}<br/></td>";
                echo "</tr>".PHP_EOL;
              }
            
                // ************************* Show Totals at the Bottom of the Screen *************************
                echo '<tr><td></td><td></td><td></td><td></td><td>Total Time Connected: <strong>'.seconds2human($secondsCounter) . '</strong>';
                echo '<br />Total Data Uploaded (Received): <strong>' . $dataUploadedTotal . ' MB</strong>';
                echo '<br />Total Data Downloaded (Sent): <strong>' . $dataDownloadedTotal . ' MB</strong></td></tr>';
            
            
            }
            
            // Close the file with the usernames
            fclose($fh);
            
            echo "</tbody></table></div>";
            $the_html_code = ob_get_clean();
            echo "</div>";
            
            // ************************* Build User List and Date Popup Picker *************************
            $arrUserlist = file("$userFilename", FILE_IGNORE_NEW_LINES);
            echo "<form method='post' action='vpn_openvpn_userlogging.php'>";
            
            //Generate Name Selection Drop Down
            //echo "<select name='user_selected'><option selected='selected'>$nameSelected</option>";
            echo "<select name='user_selected'><option selected='selected'>Select User</option>";
            foreach(array_unique($arrUserlist) as $item){
                    ?><option value="<?php echo $item; ?>"><?php echo $item; ?></option><?php
            }
            echo "</select>";
            
            //POPUP DATE PICKER
            echo "<input type='text' name='input-pop' id='input-pop' placeholder='Select Date'/>";
            
            //Submit Button
            echo "<input type='submit' value='Submit'></form>";
            
            echo $the_html_code;  // ************************* HTML Code is compiled.  Send it back to the top. *************************
            
            ?>
            
            
            
            
            <!--- ************************* Javascript for Date Picker ************************* ---> 
            <script>
               window.addEventListener("load", function(){
                  picker.attach({
                     target: "input-pop"
                  });
               });
            </script>
            
            
            <script>
            var picker = {
              // (A) ATTACH DATEPICKER TO TARGET
              // target : datepicker will populate this field
              // container : datepicker will be generated in this container
              // startmon : start on Monday (default false)
              // disableday : array of days to disable, e.g. [2,7] to disable Tue and Sun
              attach : function (opt) {
                // (A1) CREATE NEW DATEPICKER
                var dp = document.createElement("div");
                dp.dataset.target = opt.target;
                dp.dataset.startmon = opt.startmon ? "1" : "0";
                dp.classList.add("picker");
                if (opt.disableday) {
                  dp.dataset.disableday = JSON.stringify(opt.disableday);
                }
            
                // (A2) DEFAULT TO CURRENT MONTH + YEAR - NOTE: UTC+0!
                var today = new Date(),
                    thisMonth = today.getUTCMonth(), // Note: Jan is 0
                    thisYear = today.getUTCFullYear(),
                    months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
            
                // (A3) MONTH SELECT
                var select = document.createElement("select"),
                    option = null;
                select.classList.add("picker-m");
                for (var mth in months) {
                  option = document.createElement("option");
                  option.value = parseInt(mth) + 1;
                  option.text = months[mth];
                  select.appendChild(option);
                }
                select.selectedIndex = thisMonth;
                select.addEventListener("change", function(){ picker.draw(this); });
                dp.appendChild(select);
            
                // (A4) YEAR SELECT
                var yRange = 10; // Year range to show, I.E. from thisYear-yRange to thisYear+yRange
                select = document.createElement("select");
                select.classList.add("picker-y");
                for (var y = thisYear-yRange; y < thisYear+yRange; y++) {
                  option = document.createElement("option");
                  option.value = y;
                  option.text = y;
                  select.appendChild(option);
                }
                select.selectedIndex = yRange;
                select.addEventListener("change", function(){ picker.draw(this); });
                dp.appendChild(select);
            
                // (A5) DAY SELECT
                var days = document.createElement("div");
                days.classList.add("picker-d");
                dp.appendChild(days);
            
                // (A6) ATTACH DATE PICKER TO TARGET CONTAINER + DRAW THE DATES
                picker.draw(select);
            
                // (A6-I) INLINE DATE PICKER
                if (opt.container) { document.getElementById(opt.container).appendChild(dp); }
            
                // (A6-P) POPUP DATE PICKER
                else {
                  // (A6-P-1) MARK THIS AS A "POPUP"
                  var uniqueID = 0;
                  while (document.getElementById("picker-" + uniqueID) != null) {
                    uniqueID = Math.floor(Math.random() * (100 - 2)) + 1;
                  }
                  dp.dataset.popup = "1";
                  dp.dataset.dpid = uniqueID;
            
                  // (A6-P-2) CREATE WRAPPER
                  var wrapper = document.createElement("div");
                  wrapper.id = "picker-" + uniqueID;
                  wrapper.classList.add("picker-wrap");
                  wrapper.appendChild(dp);
            
                  // (A6-P-3) ATTACH ONCLICK TO SHOW/HIDE DATEPICKER
                  var target = document.getElementById(opt.target);
                  target.dataset.dp = uniqueID;
                  target.readOnly = true; // Prevent onscreen keyboar on mobile devices
                  target.onfocus = function () {
                    document.getElementById("picker-" + this.dataset.dp).classList.add("show");
                  };
                  wrapper.addEventListener("click", function (evt) {
                    if (evt.target.classList.contains("picker-wrap")) {
                      this.classList.remove("show");
                    }
                  });
            
                  // (A6-P-4) ATTACH POPUP DATEPICKER TO DOCUMENT
                  document.body.appendChild(wrapper);
                }
              },
            
            
              // (B) DRAW THE DAYS IN MONTH
              // el : HTML reference to either year or month selector
              draw : function (el) {
                // (B1) GET DATE PICKER COMPONENTS
                var parent = el.parentElement,
                    year = parent.getElementsByClassName("picker-y")[0].value,
                    month = parent.getElementsByClassName("picker-m")[0].value,
                    days = parent.getElementsByClassName("picker-d")[0];
            
                // (B2) DATE RANGE CALCULATION - NOTE: UTC+0!
                var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
                    startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // Note: Sun = 0
                    endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay(),
                    startDay = startDay==0 ? 7 : startDay,
                    endDay = endDay==0 ? 7 : endDay;
            
                // (B3) GENERATE DATE SQUARES (IN ARRAY FIRST)
                var squares = [],
                    disableday = null;
                if (parent.dataset.disableday) {
                  disableday = JSON.parse(parent.dataset.disableday);
                }
            
                // (B4) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
                if (parent.dataset.startmon=="1" && startDay!=1) {
                  for (var i=1; i<startDay; i++) { squares.push("B"); }
                }
                if (parent.dataset.startmon=="0" && startDay!=7) {
                  for (var i=0; i<startDay; i++) { squares.push("B"); }
                }
            
                // (B5) DAYS OF MONTH
                // (B5-1) ALL DAYS ENABLED, JUST ADD
                if (disableday==null) {
                  for (var i=1; i<=daysInMonth; i++) { squares.push([i, false]);  }
                }
            
                // (B5-2) SOME DAYS DISABLED
                else {
                  var thisday = startDay;
                  for (var i=1; i<=daysInMonth; i++) {
                    // CHECK IF DAY IS DISABLED
                    var disabled = disableday.includes(thisday);
                    // DAY OF MONTH, DISABLED
                    squares.push([i, disabled]);
                    // NEXT DAY
                    thisday++;
                    if (thisday==8) { thisday = 1; }
                  }
                }
            
                // (B6) EMPTY SQUARES AFTER LAST DAY OF MONTH
                if (parent.dataset.startmon=="1" && endDay!=7) {
                  for (var i=endDay; i<7; i++) { squares.push("B"); }
                }
                if (parent.dataset.startmon=="0" && endDay!=6) {
                  for (var i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
                }
            
                // (B7) DRAW HTML
                var daynames = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
                if (parent.dataset.startmon=="1") { daynames.push("Sun"); }
                else { daynames.unshift("Sun"); }
            
                // (B7-1) HTML DATE HEADER
                var table = document.createElement("table"),
                    row = table.insertRow(),
                    cell = null;
                row.classList.add("picker-d-h");
                for (let d of daynames) {
                  cell = row.insertCell();
                  cell.innerHTML = d;
                }
            
                // (B7-2) HTML DATE CELLS
                var total = squares.length,
                    row = table.insertRow(),
                    today = new Date(),
                    todayDate = null;
                if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
                  todayDate = today.getUTCDate();
                }
                for (var i=0; i<total; i++) {
                  if (i!=total && i%7==0) { row = table.insertRow(); }
                  cell = row.insertCell();
                  if (squares[i] == "B") {
                    cell.classList.add("picker-d-b");
                  } else {
                    cell.innerHTML = squares[i][0];
                    // NOT ALLOWED TO CHOOSE THIS DAY
                    if (squares[i][1]) {
                      cell.classList.add("picker-d-dd");
                    }
                    // ALLOWED TO CHOOSE THIS DAY
                    else {
                      if (i == todayDate) { cell.classList.add("picker-d-td"); }
                      cell.classList.add("picker-d-d");
                      cell.addEventListener("click", function(){ picker.pick(this); });
                    }
                  }
                }
            
                // (B7-3) ATTACH NEW CALENDAR TO DATEPICKER
                days.innerHTML = "";
                days.appendChild(table);
              },
            
              // (C) CHOOSE A DATE
              // el : HTML reference to selected date cell
              pick : function (el) {
                // (C1) GET ALL COMPONENTS
                var parent = el.parentElement;
                while (!parent.classList.contains("picker")) {
                  parent = parent.parentElement;
                }
            
                // (C2) GET FULL SELECTED YEAR MONTH DAY
                var year = parent.getElementsByClassName("picker-y")[0].value,
                    month = parent.getElementsByClassName("picker-m")[0].value,
                    day = el.innerHTML;
            
                // YYYY-MM-DD FORMAT - CHANGE FORMAT HERE IF YOU WANT !
                if (parseInt(month)<10) { month = "0" + month; }
                if (parseInt(day)<10) { day = "0" + day; }
                var fullDate = year + "-" + month + "-" + day;
            
                // (C3) UPDATE SELECTED DATE
                document.getElementById(parent.dataset.target).value = fullDate;
            
                // (C4) POPUP ONLY - CLOSE THE POPUP
                if (parent.dataset.popup == "1") {
                  document.getElementById("picker-" + parent.dataset.dpid).classList.remove("show");
                }
              }
            };
            </script>
            
            </body>
            </html>
            
            <?php include("foot.inc");?>
            

            Here is the output:
            b964b3c0-2ce5-4289-a072-e4c5e75869c3-image.png

            I don't know if I have the ability to actually integrate it at all. I'm guessing I'll need to submit this somewhere but I don't know what that process is.

            Our clients have been finding that allowing employees work from home isn't nearly as effective as having them in the office. We've been asked by several companies now to see how much the employees are actually connecting in over the VPN to get their work done. Please let me know if this is useful for you or if you have any feedback.

            noplanN 1 Reply Last reply Reply Quote 0
            • noplanN
              noplan @Stewart
              last edited by

              @stewart

              I'm gonna test it as soon as I'm back in office

              1 Reply Last reply Reply Quote 0
              • P
                psp
                last edited by

                @Stewart
                Very appreciated here. Thanks!

                1 Reply Last reply Reply Quote 0
                • S
                  Stewart
                  last edited by

                  I'm finishing up some updates. It includes selecting a date range instead of a particular day and defaults to the current month instead of showing the whole log file when it opens. I have some out of office appointments today but hope to be able to post the update later today.

                  1 Reply Last reply Reply Quote 1
                  • S
                    Stewart
                    last edited by

                    Here is the updated /usr/local/www/vpn_openvpn_userlogging.php

                    <?php
                    /*
                     * vpn_openvpn_userlogging.php
                     *
                     * Sept 2021
                     */
                    
                    ##|+PRIV
                    ##|*IDENT=page-openvpn-userlogging
                    ##|*NAME=OpenVPN: User Logging
                    ##|*DESCR=View OpenVPN User Connections.
                    ##|*MATCH=vpn_openvpn_userlogging.php*
                    ##|-PRIV
                    
                    require_once("guiconfig.inc");
                    require_once("openvpn.inc");
                    require_once("pfsense-utils.inc");
                    require_once("pkg-utils.inc");
                    
                    global $openvpn_topologies, $openvpn_tls_modes;
                    
                    $shortcut_section = "openvpn";
                    include("head.inc");
                    
                    $tab_array = array();
                    $tab_array[] = array(gettext("Servers"), false, "vpn_openvpn_server.php");
                    $tab_array[] = array(gettext("Clients"), true, "vpn_openvpn_client.php");
                    $tab_array[] = array(gettext("Client Specific Overrides"), false, "vpn_openvpn_csc.php");
                    $tab_array[] = array(gettext("Wizards"), false, "wizard.php?xml=openvpn_wizard.xml");
                    add_package_tabs("OpenVPN", $tab_array);
                    display_top_tabs($tab_array);
                    ?>
                    
                    <head><style>
                    /* ************************* This style is used for the popup date picker ************************* */
                    /* (A) POPUP */
                    .picker-wrap {
                      position: fixed;
                      top: 0;
                      left: 0;
                      width: 100vw;
                      height: 100vh;
                      background: rgba(0,0,0,0.5);
                      opacity: 0;
                      visibility: hidden;
                      transition: opacity 0.2s;
                    }
                    .picker-wrap.show {
                      opacity: 1;
                      visibility: visible;
                    }
                    .picker-wrap .picker {
                      margin: 50vh auto 0 auto;
                      transform: translateY(-50%);
                    }
                    
                    /* (B) CONTAINER */
                    .picker {
                      max-width: 300px;
                      background: #444444;
                      padding: 10px;
                    }
                    
                    /* (C) MONTH + YEAR */
                    .picker-m, .picker-y {
                      width: 50%;
                      padding: 5px;
                      box-sizing: border-box;
                      font-size: 16px;
                    }
                    
                    /* (D) DAY */
                    .picker-d table {
                      color: #fff;
                      border-collapse: separate;
                      width: 100%;
                      margin-top: 10px;
                    }
                    .picker-d table td {
                      width: 14.28%; /* 7 EQUAL COLUMNS */
                      padding: 5px;
                      text-align: center;
                    }
                    /* HEADER CELLS */
                    .picker-d-h td {
                      font-weight: bold;
                    }
                    /* BLANK DATES */
                    .picker-d-b {
                      background: #4e4e4e;
                    }
                    /* TODAY */
                    .picker-d-td {
                      background: #d84f4f;
                    }
                    /* PICKABLE DATES */
                    .picker-d-d:hover {
                      cursor: pointer;
                      background: #a33c3c;
                    }
                    /* UNPICKABLE DATES */
                    .picker-d-dd {
                      color: #888;
                      background: #4e4e4e;
                    }
                    /* ************************* End style used for the popup date picker ************************* */
                    </style></head>
                    
                    
                    
                    <?php
                    // ************************* PHP Functions *************************
                    // Function to calculate time from seconds to human readable format
                    function seconds2human($ss) {
                    	$s = $ss%60;
                    	$m = floor(($ss%3600)/60);
                    	$h = floor(($ss%86400)/3600);
                    	$d = floor(($ss%2592000)/86400);
                    	$M = floor($ss/2592000);
                    	return "$d days $h hours $m minutes $s seconds";
                    }
                    
                    // ************************* Global Variables *************************
                    $logFilename = '/var/log/OpenVPN_Users.log';            //Log to be read in to generate information from
                    $userFilename = '/tmp/OpenVPN_UniqueUsers.log';         //Scratchpad that stores usernames that then get read back into an array
                    $secondsCounter = 0;                                    //Running Tally of Time
                    $dataUploadedTotal = 0;                                 //Running Tally of Data Uploaded
                    $dataDownloadedTotal = 0;                               //Running Tally of Data Downloaded
                    $passesDateFilter = "";					// 
                    $dataArray = [];                                        //Used to store all
                    
                    
                    
                    // ************************* Pre-Fill User Option Boxes *************************
                    // Pre-Fill Start Date Selection Options					//
                    if (isset($_POST["input-startpop"])) {						// If Submit is pressed
                    	if ($_POST["input-startpop"]=="") {					//
                    		$startDateSelected = date('Y-m-01');				// 	If the Start Date info is blank, set it to the first of the current calendar month
                    	}else{									//
                    		$startDateSelected = $_POST["input-startpop"];			// 	Or If Start Date is filled, pre-fill the Start Date with the info carried from POST
                    	}									//
                    }else{										//
                    	$startDateSelected = date('Y-m-01');					// Or on page load fill the Start Date with the first of the current calendar month.
                    }										//
                    
                    // Pre-Fill End Date Selection Options						//
                    if (isset($_POST["input-endpop"])) {						// If Submit is pressed
                    	if ($_POST["input-endpop"]=="") {					//
                    		$endDateSelected = date('Y-m-31');				// 	If the End Date info is blank, set it to the last of the current calendar month
                    	}else{									//
                    		$endDateSelected = $_POST["input-endpop"];			// 	Or If the End Date is filled, pre-fill the Start Date with the info carried from POST
                    	}									//
                    }else{										//
                    	$endDateSelected = date('Y-m-31');					// On Page load fill the End Date with the last of the current calendar month.
                    }										//
                    
                    // Pre-Fill Shown Option for Users Dropdown					//
                    if (isset($_POST["user_selected"])) {						// If Submit is pressed
                    	$nameSelected = $_POST["user_selected"];				//Receive the user selected and filter based on it
                    }else{										//
                    	$nameSelected = "All Users";						//Receive the user selected and filter based on it
                    }										//
                    
                    
                    
                    // Delete the $userFilename log so it can be filled fresh with each run
                    // This could be moved to the end but it's been left at the top for diagnostic purposes
                    unlink($userFilename);
                    
                    
                    
                    // ************************* End of Functions, Variables, and Pre-Work *************************
                    ?>
                    
                    
                    
                    <body><div class='panel panel-default'><div class='panel-heading'><h2 class='panel-title'>
                    <?=gettext('Client Connections')?>
                    
                    <?
                    // ************************* HTML Store and Load Point *************************
                    ob_start(); ?>
                    
                    
                    
                    </h2></div>
                    <div class="table-responsive">
                    <table id="tblResults" class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
                    <thead><tr><th width=8%>USER</th><th width=12%>EVENT</th><th width=13%>EVENT DATE</th><th width=7%>EVENT TIME</th><th width=60%>INFORMATION</th></tr></thead><tbody>
                    
                    <?php
                    $fh = fopen("{$userFilename}", "a");						// Opens a scratchpad that stores usernames that then get read back into an array
                    if (($h = fopen("{$logFilename}", "r")) !== FALSE)				// Opens the logfile created by the connect.sh and disconnect.sh scripts to parse
                    {
                      // ************************* Open the file for reading  and loop through each line *************************
                      while (($data = fgetcsv($h, 500, ";")) !== FALSE)				// Go through line of log file and break out on each semicolon
                      {
                        // ************************* Assign the Array Information *************************
                        // Assign the $data record to variables for more readable code
                        $recUser    = $data[0];							// Store User in arr[0]
                        $recEvent   = $data[1];							// Store Event
                        $recDate    = $data[2];							// Store Event Date
                        $recTime    = $data[3];							// Store Event Time
                        $recInfo    = $data[4];							// Store Event Information
                    
                        fwrite($fh,substr(htmlspecialchars($recUser)."\n",6));			// Write the connected user to scratch file to be pulled into the user list later
                    
                        // ************************* Perform Checks to see if row should be displayed *************************
                        $startDateConv=strtotime($startDateSelected);				// Set start date value for comparison
                        $endDateConv=strtotime($endDateSelected);					// Set end date value for comparison
                        $recDateConv=strtotime($recDate);						// Set the date value of the received entry for comparison
                    
                        // ************************* Perform Evaluation Checks *************************
                        $passesNameFilter = ($nameSelected == "" || $nameSelected == "All Users" || $nameSelected == substr(htmlspecialchars($recUser),6));
                        $passesDateFilter = ($recDateConv >= $startDateConv) && ($recDateConv <= $endDateConv);							
                    
                        // If either of the filters fails to pass...
                        if($passesNameFilter == false || $passesDateFilter == false)
                        {
                          // Continue on and exit the while loop if name/date criterea aren't met
                          continue;
                        }
                    
                        // If both filters pass, capture the raw row in $dataArray
                        $dataArray[] = $data;
                    
                    
                    
                        // ************************* For a row to be displayed, prepare the informatin *************************
                        // Prepare the data from recdata (received data) to htmldata (data to be used in formatting and parsing)
                        $htmlUser = substr(htmlspecialchars($recUser),6);
                        $htmlEvent = htmlspecialchars($recEvent);
                        $htmlTime = substr(htmlspecialchars($recTime),3);
                        $htmlInfo = htmlspecialchars($recInfo);
                        $htmlDate = trim(htmlspecialchars($recDate));
                    
                        // Event Info - Parse the Information Column Depending on Event Type
                        if (strpos($recEvent,"DISCONNECTED") !== false){
                          //Manipulate Time
                          $seconds=strstr(substr(htmlspecialchars($recInfo),10), "s", true);
                          $secondsCounter += $seconds;
                          //Display Data
                          $htmlInfo = str_replace("DATA","<BR/> DATA",$recInfo);
                          //Manipulate Data Uploaded
                          $dataUploaded = strstr(substr(strstr($recInfo, "): ", false),3)," ", true);
                          $dataUploadedTotal += $dataUploaded;
                          //Manipulate Data Downloaded
                          $dataDownloaded = strstr(substr(strstr(substr(strstr($recInfo, "): ", false),3), "): ", false),3)," ", true);
                          $dataDownloadedTotal += $dataDownloaded;
                        }elseif (strpos($recEvent,"CONNECTED") != false) {
                          $needle = "IP";
                          $insertCode = "<br/>";
                          $pos = strpos($recInfo, $needle);
                          $htmlInfo = substr_replace($recInfo, $insertCode , $pos , 0);
                          $htmlInfo = str_replace("INTERNAL","<BR/> INTERNAL",$recInfo);
                        }
                    
                        // ************************* Display the Information *************************
                        echo "<tr>";
                        echo "<td>{$htmlUser}<br/></td>";
                        echo "<td>{$htmlEvent}<br/></td>";
                        echo "<td>{$htmlDate}<br/></td>";
                        echo "<td>{$htmlTime}<br/></td>";
                        echo "<td>{$htmlInfo}<br/></td>";
                        echo "</tr>".PHP_EOL;
                      }
                    
                        // ************************* After all lines have looped, show totals at the bottom of the screen *************************
                        echo '<tr><td></td><td></td><td></td><td></td><td>Total Time Connected: <strong>'.seconds2human($secondsCounter) . '</strong>';
                        echo '<br />Total Data Uploaded (Received): <strong>' . $dataUploadedTotal . ' MB</strong>';
                        echo '<br />Total Data Downloaded (Sent): <strong>' . $dataDownloadedTotal . ' MB</strong></td></tr>';
                    
                    
                    }
                    
                    // Close the file with the usernames
                    fclose($fh);
                    
                    echo "</tbody></table></div>";
                    $the_html_code = ob_get_clean();
                    echo "</div>";
                    
                    // ************************* Build User List and Date Popup Picker *************************
                    $arrUserlist = file("$userFilename", FILE_IGNORE_NEW_LINES);
                    echo "<form method='post' action='vpn_openvpn_userlogging.php'>";
                    
                    // ************************* Generate Name Selection Drop Down *************************
                    echo "<select name='user_selected'><option selected='selected'>$nameSelected</option>";
                    ?><option value="All Users">All Users</option><?php
                    foreach(array_unique($arrUserlist) as $item){
                            ?><option value="<?php echo $item; ?>"><?php echo $item; ?></option><?php
                    }
                    echo "</select>";
                    
                    // ************************* User Interaction Secion *************************
                    echo "<input type='text' name='input-startpop' id='input-startpop' placeholder='".$startDateSelected."'/>";	// Start Date Pop-up Picker
                    echo "<input type='text' name='input-endpop' id='input-endpop' placeholder='".$endDateSelected."'/>";		// Stop Date Pop-up Picker
                    echo "<input type='submit' value='Submit'></form>";								// Submit Button
                    
                    
                    
                    // ************************* HTML Code is compiled.  Send it back to the top. *************************
                    echo $the_html_code;  
                    
                    ?>
                    
                    
                    
                    
                    <!--- ************************* Javascript for Date Picker ************************* ---> 
                    <script>
                       window.addEventListener("load", function(){
                          picker.attach({
                             target: "input-startpop"
                          });
                       });
                    </script>
                    <script>
                       window.addEventListener("load", function(){
                          picker.attach({
                             target: "input-endpop"
                          });
                       });
                    </script>
                    
                    
                    <script>
                    var picker = {
                      // (A) ATTACH DATEPICKER TO TARGET
                      // target : datepicker will populate this field
                      // container : datepicker will be generated in this container
                      // startmon : start on Monday (default false)
                      // disableday : array of days to disable, e.g. [2,7] to disable Tue and Sun
                      attach : function (opt) {
                        // (A1) CREATE NEW DATEPICKER
                        var dp = document.createElement("div");
                        dp.dataset.target = opt.target;
                        dp.dataset.startmon = opt.startmon ? "1" : "0";
                        dp.classList.add("picker");
                        if (opt.disableday) {
                          dp.dataset.disableday = JSON.stringify(opt.disableday);
                        }
                    
                        // (A2) DEFAULT TO CURRENT MONTH + YEAR - NOTE: UTC+0!
                        var today = new Date(),
                            thisMonth = today.getUTCMonth(), // Note: Jan is 0
                            thisYear = today.getUTCFullYear(),
                            months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
                    
                        // (A3) MONTH SELECT
                        var select = document.createElement("select"),
                            option = null;
                        select.classList.add("picker-m");
                        for (var mth in months) {
                          option = document.createElement("option");
                          option.value = parseInt(mth) + 1;
                          option.text = months[mth];
                          select.appendChild(option);
                        }
                        select.selectedIndex = thisMonth;
                        select.addEventListener("change", function(){ picker.draw(this); });
                        dp.appendChild(select);
                    
                        // (A4) YEAR SELECT
                        var yRange = 10; // Year range to show, I.E. from thisYear-yRange to thisYear+yRange
                        select = document.createElement("select");
                        select.classList.add("picker-y");
                        for (var y = thisYear-yRange; y < thisYear+yRange; y++) {
                          option = document.createElement("option");
                          option.value = y;
                          option.text = y;
                          select.appendChild(option);
                        }
                        select.selectedIndex = yRange;
                        select.addEventListener("change", function(){ picker.draw(this); });
                        dp.appendChild(select);
                    
                        // (A5) DAY SELECT
                        var days = document.createElement("div");
                        days.classList.add("picker-d");
                        dp.appendChild(days);
                    
                        // (A6) ATTACH DATE PICKER TO TARGET CONTAINER + DRAW THE DATES
                        picker.draw(select);
                    
                        // (A6-I) INLINE DATE PICKER
                        if (opt.container) { document.getElementById(opt.container).appendChild(dp); }
                    
                        // (A6-P) POPUP DATE PICKER
                        else {
                          // (A6-P-1) MARK THIS AS A "POPUP"
                          var uniqueID = 0;
                          while (document.getElementById("picker-" + uniqueID) != null) {
                            uniqueID = Math.floor(Math.random() * (100 - 2)) + 1;
                          }
                          dp.dataset.popup = "1";
                          dp.dataset.dpid = uniqueID;
                    
                          // (A6-P-2) CREATE WRAPPER
                          var wrapper = document.createElement("div");
                          wrapper.id = "picker-" + uniqueID;
                          wrapper.classList.add("picker-wrap");
                          wrapper.appendChild(dp);
                    
                          // (A6-P-3) ATTACH ONCLICK TO SHOW/HIDE DATEPICKER
                          var target = document.getElementById(opt.target);
                          target.dataset.dp = uniqueID;
                          target.readOnly = true; // Prevent onscreen keyboar on mobile devices
                          target.onfocus = function () {
                            document.getElementById("picker-" + this.dataset.dp).classList.add("show");
                          };
                          wrapper.addEventListener("click", function (evt) {
                            if (evt.target.classList.contains("picker-wrap")) {
                              this.classList.remove("show");
                            }
                          });
                    
                          // (A6-P-4) ATTACH POPUP DATEPICKER TO DOCUMENT
                          document.body.appendChild(wrapper);
                        }
                      },
                    
                    
                      // (B) DRAW THE DAYS IN MONTH
                      // el : HTML reference to either year or month selector
                      draw : function (el) {
                        // (B1) GET DATE PICKER COMPONENTS
                        var parent = el.parentElement,
                            year = parent.getElementsByClassName("picker-y")[0].value,
                            month = parent.getElementsByClassName("picker-m")[0].value,
                            days = parent.getElementsByClassName("picker-d")[0];
                    
                        // (B2) DATE RANGE CALCULATION - NOTE: UTC+0!
                        var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
                            startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // Note: Sun = 0
                            endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay(),
                            startDay = startDay==0 ? 7 : startDay,
                            endDay = endDay==0 ? 7 : endDay;
                    
                        // (B3) GENERATE DATE SQUARES (IN ARRAY FIRST)
                        var squares = [],
                            disableday = null;
                        if (parent.dataset.disableday) {
                          disableday = JSON.parse(parent.dataset.disableday);
                        }
                    
                        // (B4) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
                        if (parent.dataset.startmon=="1" && startDay!=1) {
                          for (var i=1; i<startDay; i++) { squares.push("B"); }
                        }
                        if (parent.dataset.startmon=="0" && startDay!=7) {
                          for (var i=0; i<startDay; i++) { squares.push("B"); }
                        }
                    
                        // (B5) DAYS OF MONTH
                        // (B5-1) ALL DAYS ENABLED, JUST ADD
                        if (disableday==null) {
                          for (var i=1; i<=daysInMonth; i++) { squares.push([i, false]);  }
                        }
                    
                        // (B5-2) SOME DAYS DISABLED
                        else {
                          var thisday = startDay;
                          for (var i=1; i<=daysInMonth; i++) {
                            // CHECK IF DAY IS DISABLED
                            var disabled = disableday.includes(thisday);
                            // DAY OF MONTH, DISABLED
                            squares.push([i, disabled]);
                            // NEXT DAY
                            thisday++;
                            if (thisday==8) { thisday = 1; }
                          }
                        }
                    
                        // (B6) EMPTY SQUARES AFTER LAST DAY OF MONTH
                        if (parent.dataset.startmon=="1" && endDay!=7) {
                          for (var i=endDay; i<7; i++) { squares.push("B"); }
                        }
                        if (parent.dataset.startmon=="0" && endDay!=6) {
                          for (var i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
                        }
                    
                        // (B7) DRAW HTML
                        var daynames = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
                        if (parent.dataset.startmon=="1") { daynames.push("Sun"); }
                        else { daynames.unshift("Sun"); }
                    
                        // (B7-1) HTML DATE HEADER
                        var table = document.createElement("table"),
                            row = table.insertRow(),
                            cell = null;
                        row.classList.add("picker-d-h");
                        for (let d of daynames) {
                          cell = row.insertCell();
                          cell.innerHTML = d;
                        }
                    
                        // (B7-2) HTML DATE CELLS
                        var total = squares.length,
                            row = table.insertRow(),
                            today = new Date(),
                            todayDate = null;
                        if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
                          todayDate = today.getUTCDate();
                        }
                        for (var i=0; i<total; i++) {
                          if (i!=total && i%7==0) { row = table.insertRow(); }
                          cell = row.insertCell();
                          if (squares[i] == "B") {
                            cell.classList.add("picker-d-b");
                          } else {
                            cell.innerHTML = squares[i][0];
                            // NOT ALLOWED TO CHOOSE THIS DAY
                            if (squares[i][1]) {
                              cell.classList.add("picker-d-dd");
                            }
                            // ALLOWED TO CHOOSE THIS DAY
                            else {
                              if (i == todayDate) { cell.classList.add("picker-d-td"); }
                              cell.classList.add("picker-d-d");
                              cell.addEventListener("click", function(){ picker.pick(this); });
                            }
                          }
                        }
                    
                        // (B7-3) ATTACH NEW CALENDAR TO DATEPICKER
                        days.innerHTML = "";
                        days.appendChild(table);
                      },
                    
                      // (C) CHOOSE A DATE
                      // el : HTML reference to selected date cell
                      pick : function (el) {
                        // (C1) GET ALL COMPONENTS
                        var parent = el.parentElement;
                        while (!parent.classList.contains("picker")) {
                          parent = parent.parentElement;
                        }
                    
                        // (C2) GET FULL SELECTED YEAR MONTH DAY
                        var year = parent.getElementsByClassName("picker-y")[0].value,
                            month = parent.getElementsByClassName("picker-m")[0].value,
                            day = el.innerHTML;
                    
                        // YYYY-MM-DD FORMAT - CHANGE FORMAT HERE IF YOU WANT !
                        if (parseInt(month)<10) { month = "0" + month; }
                        if (parseInt(day)<10) { day = "0" + day; }
                        var fullDate = year + "-" + month + "-" + day;
                    
                        // (C3) UPDATE SELECTED DATE
                        document.getElementById(parent.dataset.target).value = fullDate;
                    
                        // (C4) POPUP ONLY - CLOSE THE POPUP
                        if (parent.dataset.popup == "1") {
                          document.getElementById("picker-" + parent.dataset.dpid).classList.remove("show");
                        }
                      }
                    };
                    </script>
                    <!--- ************************* End Javascript for Date Picker ************************* ---> 
                    
                    </body>
                    </html>
                    
                    <?php include("foot.inc");?>
                    
                    

                    The update includes a few enhancements such as date range, better selection defaults, and log file limiting as well as improved notation. Give a spin and let me know how it works out for you.

                    1 Reply Last reply Reply Quote 4
                    • S
                      Stewart
                      last edited by

                      @noplan @psp

                      Have you had a chance to test out the scripts? Any feedback?

                      P W 2 Replies Last reply Reply Quote 0
                      • P
                        psp @Stewart
                        last edited by psp

                        @stewart said in Email Notification - OpenVPN Client Connect (Common Name):

                        @psp

                        Have you had a chance to test out the scripts? Any feedback?

                        Sure! I only changed the timestamp to use H24 notation. No issues so far:

                        2021-10-12_225319.png

                        1 Reply Last reply Reply Quote 0
                        • W
                          WVA @Stewart
                          last edited by

                          @stewart Thank you for this script, works like a charm !

                          Regarding the log file limiting you mention, I can't seem to find this in your code ?
                          I think it would be good to set a maximum filesize (and/or even truncate files) ? What would be best practice in implementing this ?

                          And what if one has multiple OpenVPN servers, would grouping by server be possible ? Maybe in the same manner as usernames are being propped in an array and end user can choose from by dropdown list ?

                          S 1 Reply Last reply Reply Quote 0
                          • S
                            Stewart @WVA
                            last edited by

                            Sorry, I didn't see a message that someone had posted in here.

                            Regarding the log file limiting you mention, I can't seem to find this in your code ?
                            I think it would be good to set a maximum filesize (and/or even truncate files) ? What would be best practice in implementing this ?

                            Very good question and one I don't have an answer to. I haven't really looked at it other than to know it needs to be done, otherwise the file will just continue to grow. At that point, it may be a cron job but I'd assume it would be need to be handled like the rest of the system logs and I have no idea how to do that.

                            I can tell you that the site I developed this on has several VPN users that connect and disconnect frequently throughout the day. Their /var/log/OpenVPN_Users.log file appears to be growing at about 10K / week or roughly 1 MB every other year. Over the expected 7 year lifecycle I suppose it will only be about 3.5MB - 4MB in size. Hardly a concern for this system but for systems with many users constantly going in and out it could get larger, faster.

                            And what if one has multiple OpenVPN servers, would grouping by server be possible ? Maybe in the same manner as usernames are being propped in an array and end user can choose from by dropdown list ?

                            Another good question. I'm not sure if that is a variable that can be extracted. Since the script files are unique to the server you could theoretically just rename the connect.sh, disconnect.sh, OpenVPN_Users.log, and vpn_openvpn_userlogging.php files and then update the pointers in the files to the new locations. That would cause you to have a second page that tracks the second server. Off the top of my head it seems fairly simple for you to do manually, but not automatically by the script if you can't pull the name of the server from the $local_connect_value.

                            1 Reply Last reply Reply Quote 1
                            • S
                              Stewart
                              last edited by

                              I had an issue with a user where the case changed between login and logout. No idea why but it created some oddities in the logs so I've updated the connect.sh and disconnect.sh files with a minor strtolower change.

                              connect.sh

                              #!/usr/local/bin/php -q
                              
                              <?php
                              //      Gather Info
                                      $date = date('Y-m-d');
                                      $time = date('g:i a');
                                      require_once("/etc/inc/notices.inc");
                              
                              //      Compile Info String to Send
                                      $local_connect_value = "USER: " . strtolower(getenv('common_name')) . "; CONNECTED; " . $date . "; at " .$time . "; IP CONNECTED FROM: " . getenv('trusted_ip') . " INTERNAL IP ASSIGNED: " . getenv('ifconfig_pool_remote_ip') . "\n";
                              
                              //      Send Email Notification of Event
                              //      notify_all_remote($local_connect_value);
                              
                              $filename="/var/log/OpenVPN_Users.log";
                              $fp = fopen($filename, 'a');
                              fwrite($fp,$local_connect_value);
                              fclose($fp);
                              
                              ?>
                              

                              disconnect.sh

                              #!/usr/local/bin/php -q
                              <?php
                              //      Gather Info
                                      require_once("/etc/inc/notices.inc");
                                      $date = date('Y-m-d');
                                      $time = date('g:i a');
                                      $durationSeconds = getenv('time_duration');
                              
                              //      Function to calculate time from seconds to human readable format
                                      function seconds2human($ss) {
                                              $s = $ss%60;
                                              $m = floor(($ss%3600)/60);
                                              $h = floor(($ss%86400)/3600);
                                              $d = floor(($ss%2592000)/86400);
                                              $M = floor($ss/2592000);
                                              return "$d days $h hours $m minutes $s seconds";
                                      }
                              
                              //      Compile Info String to Send
                                      $local_connect_value .= "USER: " . strtolower(getenv('common_name')) . "; DISCONNECTED; " . $date . "; at " . $time . "; DURATION: " . $durationSeconds . " seconds or " . seconds2human($durationSeconds) . " DATA UPLOADED (RECEIVED): ". round(((getenv('bytes_received'))/1048576),2) . " MB DATA DOWNLOADED (SENT): " . round(((getenv('bytes_sent'))/1048576),2) ." MB\n";
                              
                              //      Send Email Notification of Event
                              //      notify_all_remote($local_connect_value);
                              
                              
                              //      Output to Log File
                                      $filename="/var/log/OpenVPN_Users.log";
                                      $fp = fopen($filename, 'a');
                                      fwrite($fp,$local_connect_value);
                                      fclose($fp);
                              
                              ?>
                              
                              1 Reply Last reply Reply Quote 1
                              • johnpozJ johnpoz referenced this topic on
                              • johnpozJ johnpoz referenced this topic on
                              • GertjanG Gertjan referenced this topic on
                              • GertjanG Gertjan referenced this topic on
                              • S
                                Stewart
                                last edited by

                                It's been working in our environment but there's no real integration into the GUI and I need to manually type in the page each time. Is there a way to integrate it or get it integrated? I know if I go mucking around in the interface it's just going to get overwritten with the next update.

                                Also, 4 months later and the log file is roughly 169K in size so it will grow to about .5MB after a year, depending on usage. That keeps it in line with my estimate of 3.5MB-4MB after the 7 year lifecycle.

                                1 Reply Last reply Reply Quote 0
                                • A
                                  aasimenator
                                  last edited by aasimenator

                                  Our OpenVPN is using Radius Server synced with Windows Active Directory Domain server for logon. Is there any way this email notification will work in such a scenario?

                                  because currently we only get the following information, username field is blank
                                  c8d61c03-c13c-4025-97c4-3894b1a855b7-image.png

                                  GertjanG 1 Reply Last reply Reply Quote 0
                                  • GertjanG
                                    Gertjan @aasimenator
                                    last edited by

                                    @aasimenator

                                    "user_name" ?
                                    "vpn_client_ip" ?

                                    Show the script you are using.

                                    See the example "connect.sh" file above.
                                    The scripts file is called with a bunch of pre defined environment variable set.
                                    Ask Google how to print all the environment variables when it starts. Normally, if known, a variable like "common_name" should contain the name.

                                    No "help me" PM's please. Use the forum, the community will thank you.
                                    Edit : and where are the logs ??

                                    A 1 Reply Last reply Reply Quote 0
                                    • A
                                      aasimenator @Gertjan
                                      last edited by

                                      @gertjan

                                      my connect.sh is exactly what you mentioned in the post above 1646309481558

                                      i just changed the name from notify.sh to connect.sh and left disconnect.sh as is.

                                      /root/connect.sh file:

                                      #!/usr/local/bin/php -q
                                      <?php
                                      	require_once("/etc/inc/notices.inc");
                                      	$local_connect_value = " user_name: " . getenv('common_name') . " vpn_client_ip: " . getenv('ifconfig_pool_remote_ip') . " on " . date('F j, Y, g:i a');
                                      	log_error("About to send a mail : Connecting");
                                      	notify_all_remote($local_connect_value);
                                      ?>
                                      

                                      /root/disconnect.sh file:

                                      #!/usr/local/bin/php -q
                                      <?php
                                      	require_once("/etc/inc/notices.inc");
                                      	$local_connect_value .= ", during : " . getenv('time_duration') . " seconds, received : " . getenv('bytes_received') . " bytes, send : " . getenv('bytes_sent') ." bytes. DISCONNRECTED.";
                                      	log_error("About to send a mail : Disconnecting");
                                      	notify_all_remote($local_connect_value);
                                      ?>
                                      
                                      GertjanG 1 Reply Last reply Reply Quote 0
                                      • A aasimenator referenced this topic on
                                      • GertjanG
                                        Gertjan @aasimenator
                                        last edited by Gertjan

                                        @aasimenator

                                        Looks ok.
                                        What happens - I think - is that when LDAP is used, the common_name environment isn't set as it isn't known. So getenv('common_name') return 'nothing' or an empty string.

                                        I've never used LDAP, so I can't be sure.

                                        edit : again : have all the env variables printed, and see what's in there.

                                        No "help me" PM's please. Use the forum, the community will thank you.
                                        Edit : and where are the logs ??

                                        A 1 Reply Last reply Reply Quote 0
                                        • A
                                          aasimenator @Gertjan
                                          last edited by

                                          @gertjan said in Email Notification - OpenVPN Client Connect (Common Name):

                                          have all the env variables printed, and see what's in there.

                                          How do you do that?

                                          GertjanG 1 Reply Last reply Reply Quote 0
                                          • GertjanG
                                            Gertjan @aasimenator
                                            last edited by

                                            @aasimenator

                                            PHP getenv()

                                            I created a small test file /root/test.php :

                                            #!/usr/local/bin/php -q
                                            <?php
                                            print_r(getenv(), $output);
                                            echo $output;
                                            ?>
                                            

                                            Now (made it executable) :
                                            ./test.php

                                            works for me.

                                            Array
                                            (
                                                [SSH_CLIENT] => 192.168.1.2 59841 22
                                                [LOGNAME] => root
                                                [MAIL] => /var/mail/root
                                                [PATH] => /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin
                                                [PWD] => /root
                                                [TERM] => xterm
                                                [SSH_TTY] => /dev/pts/0
                                                [USER] => root
                                                [HOME] => /root
                                                [SSH_CONNECTION] => 192.168.1.2 59841 192.168.1.1 22
                                                [SHELL] => /bin/sh
                                                [BLOCKSIZE] => K
                                                [HOSTTYPE] => FreeBSD
                                                [VENDOR] => amd
                                                [OSTYPE] => FreeBSD
                                                [MACHTYPE] => x86_64
                                                [SHLVL] => 1
                                                [GROUP] => wheel
                                                [HOST] => pfsense.munetwork.net
                                                [REMOTEHOST] => 192.168.1.2
                                                [CLICOLOR] => true
                                                [LSCOLORS] => exfxcxdxbxegedabagacad
                                            )
                                            

                                            This was just a test - my script file

                                            Add $output to the $local_connect_value string, and have it mailed to you.
                                            Or log it :

                                            #!/usr/local/bin/php -q
                                            <?php
                                            	require_once("/etc/inc/notices.inc");
                                            	$local_connect_value = " user_name: " . getenv('common_name') . " vpn_client_ip: " . getenv('ifconfig_pool_remote_ip') . " on " . date('F j, Y, g:i a');
                                            	print_r(getenv(), $output);
                                            	log_error($output);
                                            	log_error("About to send a mail : Connecting");
                                            	notify_all_remote($local_connect_value);
                                            ?>
                                            

                                            No "help me" PM's please. Use the forum, the community will thank you.
                                            Edit : and where are the logs ??

                                            A 1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            Copyright 2025 Rubicon Communications LLC (Netgate). All rights reserved.