All posts by Manish

PHP QR Code Generate

In one of my projects I suddenly needed to generate QR code. I tried searching for some free QR Code generator Library, but didn’t find anything suitable and reliable except the PEAR package which I had used many years back. I was not willing to use the package as the last Alpha release was about 9 years ago. But as there were no suitable package so ultimately used that. The PHP version is 7.2.34.

Using the library is simple and pretty straightforward

Installation:

$pear install channel://pear.php.net/Image_QRCode-0.1.3

PHP Code

require_once './Image_QRCode-0.1.3/Image/QRCode.php';

$qrcode = new Image_QRCode();

$options = array( "image_type" => "png", "output_type" => "return" );

$gd_object = $qrcode->makeCode("STRING", $options);

$sx = imagesx($gd_object);

$sy = imagesy($gd_object);

//The returned object is an "image resource". Which can be saved as an image or can be used as a watermark. Below example is of watermarking which will placed at the bottom right on the main image
//imagesx($im) - $sx === the x position where the qr code will be placed
//imagesy($im) - $sy === the y position where the qr code will be placed
//0, 0 == the start point from where the QRCode image will start to be be copied - 0,0 means from the top left or start of the image - if some other value is given then it will be cropped
//imagesx($gd_object), imagesy($gd_object), == the end point of the qr code image. The current copies upto the bottom right or end point of the image. Any value lesser will cause a cropped image. //75 == is the quality of the overall image.

$im = imagecreatefromstring($data);  // imagecreatefrompng($filename)

imagecopymerge($im, $gd_object, imagesx($im) - $sx, imagesy($im) - $sy, 0, 0, imagesx($gd_object), imagesy($gd_object), 75);

ob_start(); //Turn on output buffering

imagepng($im); //Generate your image

$outputImage = ob_get_contents(); // get the image as a string in a variable

ob_end_clean(); //Turn off output buffering and clean

file_put_contents("IMAGE.PNG",$outputImage);


Outgoing email problem with Roundcube, ISPConfig 3.2 and Ubuntu 20.04 on AWS

I had setup an ISPConfig system with Roundcube on Ubuntu 20.04 hosted with AWS EC2.

The setup was all fine, logged in into Roundcube and tried to send a mail. There was the problem – Roundcube says Error 250: Authentication failed.

It seems others also faced the same issue and different solutions worked for different people. I tested the most popular solutions and below are the results

  1. Changing $config[‘smtp_port’] = 587;  to   $config[‘smtp_port’] = 25; in /etc/roundcube/config.inc.php worked instantly but didn’t want to send things over plain text port
  2. Most popular solution was to remove the values of the below settings in /etc/roundcube/config.inc.php:
    $config[‘smtp_user’] = ‘%u’;
    $config[‘smtp_pass’] = ‘%p’;
    The suggested solution is to remove the %u and %p. Though it worked for many but not for me.
  3. Tried setting the $config[‘smtp_server’] = ‘localhost’;  to  $config[‘smtp_server’] = ‘tls://localhost’;
    $config[‘default_host’] = ‘localhost’;  to  $config[‘default_host’] = ‘tls://localhost’;

    But didn’t work
    With these changes was unable to login to Roundcube.
  4. Tried using $config[‘smtp_auth_type’] = ‘PLAIN’;  Didn’t work.
  5. There were also other suggestions to tweak smtpd_use_tls, smtpd_sasl_auth_enable,  smtpd_tls_auth_only etc. But none of these worked either.
  6. The configuration that worked was 
    
    $config['default_host'] = 'localhost';
    $config['smtp_server'] = 'tls://localhost';
    $config['smtp_port'] = 587;
    $config['smtp_user'] = '%u';
    $config['smtp_pass'] = '%p';
    ....
    ....
    $config['imap_conn_options'] = array(
       'ssl' => array(
          'verify_peer' => false,
          'verfify_peer_name' => false,
        ),
    );
    
    $config['smtp_conn_options'] = array(
       'ssl' => array(
           'verify_peer' => false,
           'verify_peer_name' => false,
        ),
    );

    Which indicates it was a SSL issue. But changing the default SSL set by Postfix is a lot work, someday will delve into it.

There was another issue due to which mails were not going out sometimes. The error was “Our
system has detected that 550-5.7.1 this message does not meet IPv6 sending
guidelines regarding PTR 550-5.7.1 records and authentication. Please
review 550-5.7.1

Needed to set
inet_protocols = all  to  inet_protocols = ipv4 in /etc/postfix/main.cf  to force Postfix to use IPv4 only

ISPConfig 3 Showing Default Site

After a fresh install of ISPConfig 3 I added some site. Now the strange problem was HTTP version was always opening the default Apache page. But the HTTPS version was opening the proper site.

Solution:

Disable the default website

a2dissite 000-default.conf
The reason for HTTP version opening default site and HTTPS opening proper site:

When Apache is installed it installs a default website (normally /etc/apache2/sites-enabled/000-default.conf). This default website doesn’t have any HTTPS version, only the HTTP version is installed.

Now when the HTTP version of my site was being requested it was opening the HTTP version of the default site as the HTTP version was existing. But as there was no HTTPS version of the default site so when the HTTPS version  of my site was being called no default site was matching and hence it opened the proper site.

Pure-FTPd Error on Amazon EC2

Pure-FTPd error “500 I won’t open a connection to <IP ADDRESS>” OR “Server sent passive reply with unroutable address. Using server address instead.

This happens when the server is beyond a NAT like the Amazon EC2. The most posted solution on the internet is to fallback to “Passive” mode in the FTP client. But in my case that didn’t help and still I got the same error. After more digging found the solution.

Need to create two files ForcePassiveIP and PassivePortRange and put the port range and the Public IP of the server.

echo "40110 40210" > /etc/pure-ftpd/conf/PassivePortRange 

echo "1.2.3.4" > /etc/pure-ftpd/conf/ForcePassiveIP

1.2.3.4 is the external IP address of the EC2 instance.

Didn’t make any changes to /etc/pure-ftpd/pure-ftpd.conf. Specially didn’t restrict or set the “IPv4 Only “. With IPv4 only I face problems with some internet connections which uses IPv6.

Settings for FileZilla

Encryption: require explicit FTP over TLS
Transfer mode: Passive (PASV)

Restart Pure-FTPd. The command may vary based on which package has been used.

service pure-ftpd-mysql stop
service pure-ftpd-mysql start

Hope this helps someone.

Adding a second disk and quota in Amazon EC2

This article is based on Ubuntu 20.04.

AWS has good documentation on how to create and add the disk to the instance so not mentioning those steps here.

Once the disk is added to the system verify that it is added and what is the name it is being shown as.

Steps for adding the disk to the system – that is making it ready for mounting

  1. lsblk  --- to check the disk has been added and the name
  2. mkfs -t ext4 /dev/<disk name>    --- Please Note - xfs disks gives error when quota options are added in the fstab files. e.g - mkfs -t ext4 /dev/nvme1n1
  3. mount /dev/<disk name> <mount point>  ---  e.g - mount /dev/nvme1n1 /data
  4. blkid --- check the UUID of the disk
  5. Add entry to fstab
    
    UUID=axxf131c-xxxx-xxxx-8xxx-ec978dxxxxxx /data ext4 defaults,nofail 0 2
    
    Replace /data with your mount point name
    
    nofail will ensure that the system boots even if the disk mounting fails (like when disk removed)
  6. umount <mount point>  ---  umount /data
  7. mount -a  --- Please note - wrong entry in the fstab can make the system unbootable. So please ensure that there are no errors. And don't reboot without resolving errors.  

 

Now the steps for turning on Quota

  1. mount -o remount <disk partition>  e.g - mount -o remount /data
  2. quotacheck -avugm
  3. quotaon -avug
  4. Edit the fstab file and add the necessary
    • nano /etc/fstab
    • add the following to the end of the existing parameters ,usrjquota=quota.user,grpjquota=quota.group,jqfmt=vfsv0
    • Save the file
    • Please note - wrong entry in the fstab file can make the system unusable. Please check the file first before rebooting.  Use mount -a to check if the entries in fstab file is good.

Example:

Disk Quota on Amazon EC2

The Linux AMIs available for AWS EC2 may not have the packages required for activating Disk Quota.

This article is based on Ubuntu 20.04.

There are many articles and suggestions on the internet. Following one of the suggestions caused my system to become read-only. Lastly found a solution which looks the most legit, simple and works perfect.

It needs installation of the package “linux-modules-extra-aws

  1. apt-get install linux-modules-extra-aws
  2. apt-get -y install quota quotatool
  3. mount -o remount <disk partition>  e.g - mount -o remount /
  4. quotacheck -avugm
  5. quotaon -avug
  6. Edit the fstab file and add the necessary
    • nano /etc/fstab
    • add the following to the end of the existing parameters ,usrjquota=quota.user,grpjquota=quota.group,jqfmt=vfsv0
    • Save the file
    • Please note - wrong entry in the fstab file can make the system unusable. Please check the file first before rebooting.  Use mount -a to check if the entries in fstab file is good. 

XFS in AWS EC2 for MongoDB. The easy way

Changing the filesystem on AWS EC2 is a difficult task. The easy way to use XFS filesystem on AWS EC2 for hosting MongoDB is to add an extra block storage and format it as XFS

  • First create a Block Storage and attach.
  • Provisioned IOPS SSD is a suitable one for high volume data flow.
  • Once the block storage is attached, then use the command prompt or terminal to format the disk as XFS and mount
    1. apt-get update
    2. apt-get upgrade
    3. lsblk —- to view the attached block device and confirm
    4. file -s /dev/nvme1n1 — to check if there is a filesystem already there or not. if there is no filesystem then it will return “device” . Else it will show something like this “/dev/nvme1n1: SGI XFS filesystem data (blksz 4096, inosz 512, v2 dirs)”.
    5. mkfs -t xfs /dev/nvme1n1 —– command to format the disk with XFS filesystem
    6. apt-get install xfsprogs –— command to install xfs tools
    7. mkdir /mongodata
    8. mount /dev/nvme1n1 /data
    9. mount /dev/nvme1n1 /mongodatabases/
    10. cp /etc/fstab /etc/fstab.orig
    11. blkid —– to check the UUID of the block device added
    12. nano /etc/fstab —– and add the device like this UUID=7xxf03xx-6xxx-4xxx-9xxx-exxxxff2xxxxf /mongodatabases xfs defaults,nofail 0 2
    13. umount /mongodatabases/ —– unmount and mount -a to check fstab entry
    14. mount -a
    15. reboot — I did a double check by rebooting and checked if the disk is still attched and the mounted
    16. df -h
    17. lsblk
    18. mount

Website Push Notification with Google Firebase

Mainly three things are needed

  • A file named firebase-messaging-sw.js placed in the root of the website. This file contains the codes for receiving the push when the browser is minimized or that particular website is closed.
  • The scripts for receiving the push
  • The server side code to send the PUSH

 

firebase-messaging-sw.js

//Base library
importScripts("https://www.gstatic.com/firebasejs/7.21.1/firebase-app.js");

//needed for PUSH Message
importScripts("https://www.gstatic.com/firebasejs/7.21.1/firebase-messaging.js");


// Your web app's Firebase configuration
//All these details is available from Firebase Console. The readymade configuration code is available also - please see the attached screenshot
var firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxx_x-xxxxxxxxxxxx",
    authDomain: "app-name-123456.firebaseapp.com",
    databaseURL: "https://app-name-123456.firebaseio.com",
    projectId: "app-name-123456",
    storageBucket: "app-name-123456.appspot.com",
    messagingSenderId: "123456789012",
    appId: "1:123456789012:web:xxxxxxxxxxxxxxxxxx",
    measurementId: "G-xxxxxxxxxxxx"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.onBackgroundMessage(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  const notificationTitle = payload.notification.title;
  const notificationOptions = {
	body: payload.notification.body,
	icon: 'https://mywebsite.com/icon.png',
	image: 'https://mywebsite.com/logo.png'
  };

  return self.registration.showNotification(notificationTitle,
	notificationOptions);
});
// [END background_handler]

Code for Receiving the Push and processing and displaying the PUSH when the page is open

At present some browsers need user interaction (request raised from an user event handler) to show the access permission popup. The best way to do is to check for permission status (granted or not) and show a Custom popup if not granted. In the custom popup put two buttons – Accept/Yes and Decline/No. On pressing Accept/Yes call the requestPermission function.

When the page is fully loaded (document ready) we are calling the updateUIForPushPermissionRequired function. Inside it we are checking if permission already granted or not. If not granted then we are showing the custom popup


<!DOCTYPE html> 
<html> 
  <head> 
    <meta charset="UTF-8"> 
  </head> 
  <body>
    <div id="notificationPopUp" class="notificationPopup" style="display:none"> 
      <div class="notif-body"> 
        <button id="closeNotification" class="uk-modal-close-default uk-icon uk-close" type="button" uk-close="" onclick="dismissnotificationPopUp()"> 
          <svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" data-svg="close-icon"> 
            <line fill="none" stroke="#000" stroke-width="1.1" x1="1" y1="1" x2="13" y2="13">
            </line> 
            <line fill="none" stroke="#000" stroke-width="1.1" x1="13" y1="1" x2="1" y2="13">
            </line> 
          </svg> 
        </button> 
        <div class="notif-logo">
          <img src="https://api.wisck.com/logo-wisck.png" alt="" width="100%" height="150">
        </div> 
        <div class="notif-content"> 
          <p class="notif-title">Allow Notifications?
          </p> 
          <p class="text_pop_up">Get notifications of new contents and updates
          </p> 
          <p class="text_pop_up">Notification option can be managed from browser settings.
          </p> 
          <div class="notif-btns"> 
            <button id="blockNotification" class="button-decline" onclick="dismissnotificationPopUp()">No
            </button> 
            <button id="allowNotification" class="button-accept" onclick="requestPermission()">Yes
            </button> 
          </div> 
        </div> 
      </div> 
    </div> 

<div id="snackbar"><p id="snackbarTitle">Title</p><p id="snackbarBody">Body</p></div>
<!-- The core Firebase JS SDK is always required and must be listed first --> <script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-app.js"> </script> <script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-messaging.js"> </script> <!-- TODO: Add SDKs for Firebase products that you want to use https://firebase.google.com/docs/web/setup#available-libraries --> <script> // Your web app's Firebase configuration var firebaseConfig = { apiKey: "xxxxxxxxxxxxxxxxxxx_x-xxxxxxxxxxxx", authDomain: "app-name-123456.firebaseapp.com", databaseURL: "https://app-name-123456.firebaseio.com", projectId: "app-name-123456", storageBucket: "app-name-123456.appspot.com", messagingSenderId: "123456789012", appId: "1:123456789012:web:xxxxxxxxxxxxxxxxxx", measurementId: "G-xxxxxxxxxxxx" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); firebase.analytics(); const messaging = firebase.messaging(); messaging.onMessage((payload) => { console.log('Message received. ', payload); var x = document.getElementById("snackbar"); // Add the "show" class to DIV x.className = "show"; $("#snackbarTitle").html(payload.notification.title); $("#snackbarBody").html(payload.notification.body); // After 3 seconds, remove the show class from DIV setTimeout(function() { x.className = x.className.replace("show", ""); }, 3000); // Update the UI to include the received message. }); function resetUI() { // Get registration token. Initially this makes a network call, once retrieved // subsequent calls to getToken will return from cache. messaging.getToken({ vapidKey: 'xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxx-xxxxxx-xxx' }).then((currentToken) => { if (currentToken) { sendTokenToServer(currentToken); } else { // Show permission request. console.log('No registration token available. Request permission to generate one.'); // Show permission UI. updateUIForPushPermissionRequired(); setTokenSentToServer(false); } }).catch((err) => { console.log('An error occurred while retrieving token. ', err); //showToken('Error retrieving registration token. ', err); setTokenSentToServer(false); }); } // Send the registration token your application server, so that it can: // - send messages back to this app // - subscribe/unsubscribe the token from topics function sendTokenToServer(currentToken) { if (!isTokenSentToServer()) { console.log('Sending token to server...'); // TODO(developer): Send the current token to your server. $.ajax({ url: base_url + 'update-token.php', type: 'post', data: { userId: userId, deviceToken: currentToken, deviceType: "web" }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, dataType: 'json', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With" }, success: function(response) { console.log('Token sent to server...'); } }); setTokenSentToServer(true); } else { console.log('Token already sent to server so won\'t send it again ' + 'unless it changes'); } } function isTokenSentToServer() { return window.localStorage.getItem('sentToServer') === '1'; } function setTokenSentToServer(sent) { window.localStorage.setItem('sentToServer', sent ? '1' : '0'); } function requestPermission() { $('#notificationPopUp').hide(); //hide the own permission popup - the browser will take over now console.log('Requesting permission...'); Notification.requestPermission().then((permission) => { if (permission === 'granted') { console.log('Notification permission granted.'); // TODO(developer): Retrieve a registration token for use with FCM. // In many cases once an app has been granted notification permission, // it should update its UI reflecting this. resetUI(); } else { console.log('Unable to get permission to notify.'); } }); } function deleteToken() { // Delete registration token. messaging.getToken().then((currentToken) => { messaging.deleteToken(currentToken).then(() => { console.log('Token deleted.'); setTokenSentToServer(false); // Once token is deleted update UI. resetUI(); }).catch((err) => { console.log('Unable to delete token. ', err); }); }).catch((err) => { console.log('Error retrieving registration token. ', err); showToken('Error retrieving registration token. ', err); }); } function updateUIForPushPermissionRequired() { if (Notification.permission != "granted") //if permission not granted yet then show custom popup. { $('#notificationPopUp').slideDown("fast"); //show the custom popup to ask permission. } else requestPermission(); } function dismissnotificationPopUp() { $('#notificationPopUp').hide(); } $(document).ready(function() { updateUIForPushPermissionRequired(); }) </script> </body> </html>

CSS for for the notification popup and the notifications

.notificationPopup {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
background: #fff;
z-index: 9999;
box-shadow: 1px 2px 10px rgba(0,0,0,.5);
}

.notificationPopup .notif-body {
padding: 15px;
display: flex;
}

.notificationPopup .uk-modal-close-default {
position: absolute;
right: 10px;
top: 10px;
background: 0 0;
border: 0;
}

.notificationPopup .notif-logo {
max-width: 70px;
min-width: 70px;
padding-right: 10px;
}


.notificationPopup .notif-logo img{
margin-top: 50%;
}


.notificationPopup .notif-content .notif-title {
font-size: 18px;
font-weight: 600;
margin: 10px 0;
}

.notificationPopup .notif-content .text_pop_up {
font-size: 16px;
font-weight: 400;
margin: 0;
}

.notificationPopup .notif-btns {
text-align: right;
padding-top: 5px;
}

.notificationPopup .notif-btns .button-decline {
border: 1px solid #ddd;
color: #666;
}

.notificationPopup .notif-btns .button-accept {
border: 1px solid #ec2436;
background-color: #ec2436;
color: #fff;
font-weight: 600;
}

.notificationPopup .notif-btns button {
background: 0 0;
border: 0;
padding: 5px 15px;
cursor: pointer;
margin-left: 10px;
}

.notificationPopup .uk-modal-close-default {
position: absolute;
right: 10px;
top: 10px;
background: 0 0;
border: 0;
}

.uk-close {
color: #999;
transition: .1s ease-in-out;
transition-property: all;
transition-property: color,opacity;
}


/* The snackbar - position it at the bottom and in the middle of the screen */
#snackbar {
visibility: hidden; /* Hidden by default. Visible on click */
min-width: 250px; /* Set a default minimum width */
margin-left: -125px; /* Divide value of min-width by 2 */
background-color: #333; /* Black background color */
color: #fff; /* White text color */
text-align: center; /* Centered text */
border-radius: 2px; /* Rounded borders */
padding: 16px; /* Padding */
position: fixed; /* Sit on top of the screen */
z-index: 9999; /* Add a z-index if needed */
left: 10%; /* Center the snackbar */
bottom: 30px; /* 30px from the bottom */
}

#snackbar p{
color: #fff; /* White text color */
}

#snackbarTitle{
font-weight: bold; /* White text color */
}

/* Show the snackbar when clicking on a button (class added with JavaScript) */
#snackbar.show {
visibility: visible; /* Show the snackbar */
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
However, delay the fade out process for 2.5 seconds */
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
}

/* Animations to fade the snackbar in and out */
@-webkit-keyframes fadein {
from {bottom: 0; opacity: 0;}
to {bottom: 30px; opacity: 1;}
}

@keyframes fadein {
from {bottom: 0; opacity: 0;}
to {bottom: 30px; opacity: 1;}
}

@-webkit-keyframes fadeout {
from {bottom: 30px; opacity: 1;}
to {bottom: 0; opacity: 0;}
}

@keyframes fadeout {
from {bottom: 30px; opacity: 1;}
to {bottom: 0; opacity: 0;}
}

 

Code for sending

<?php
define('SERVER_API_KEY', 'XXXXXXXX:XXXXXXXX-XXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X-');

//FOR WEB ALSO TOKENS GET GENERATED. 
$tokens = ['XXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX',
'XXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX'
];

$header = [
     'Authorization: Key= ' . SERVER_API_KEY,
     'content-Type: Application/json'
];

$notification = [
      'title' => 'Testing Notification',
      'body' => 'Testing Notification Body',
      'icon' => '',
	  'sound' => 'default',
	  'click_action' => "android.intent.action.MAIN"
];

$data = [
      'title' => 'Testing Notification',
      'body' => 'Testing Notification Body',
      'icon' => '',
	  'sound' => 'default',
];

$options = [
	'priority'=> 'high'
];

$payload = [
       'registration_ids' => $tokens,
	   'notification' => $notification,
	   'data' => $data,
	   'options' => $options
];

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://fcm.googleapis.com/fcm/send",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => json_encode( $payload ),
  CURLOPT_HTTPHEADER => $header,
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
?>

 

Google Firebase Web Push – Public VAPID Key
Google Firebase Web Push – Web app configuration

 

Please note this code will show two popups in background mode – that is because Push is being sent as “Notification”. Browser is handling that and showing one on its own. And another from the backgroundHandler code in Service Worker file. For showing only the custom popup use “Data” instead of “Notification” in the Push body.

 

How to find list of all FCM parameters https://www.kolkataonweb.com/code-bank/miscellaneous/google-fcm-all-push-notification-parameters/