Quantcast
Channel: sleeplessbeastie's notes
Viewing all 770 articles
Browse latest View live

How to import Firefox bookmarks to Nextcloud application

$
0
0

I am currently playing with floccus to store bookmarks using Nextcloud, so I have created simple shell scripts to import Nextcloud bookmarks from Firefox using code from my earlier solution to open Firefox bookmarks from OpenBox menu.

Nextcloud bookmarks and floccus - Google Chrome using data exported from Firefox
Nextcloud bookmarks and floccus - Google Chrome using data exported from Firefox

Prerequisites

Install curl utility to perform API calls.

$ sudo apt-get install curl

Install jq utility to parse JSON files.

$ sudo apt-get install jq

Install sqlite3 utility to access SQLite database.

$ sudo apt-get install sqlite3

Create application password

Create Nextcloud application password, do not use your regular credentials.

Bookmarks

I will use the following bookmarks in this article. These bookmarks are included in source code.

Mozilla Firefox>Pomoc (https://support.mozilla.org/pl/products/firefox)
Mozilla Firefox>Dostosuj Firefoksa (https://www.mozilla.org/pl/firefox/customize/)
Mozilla Firefox>Dołącz do nas (https://www.mozilla.org/pl/contribute/)
Mozilla Firefox>O Mozilli (https://www.mozilla.org/pl/about/)
Ubuntu and Free Software links>Ubuntu (http://www.ubuntu.com/)
Ubuntu and Free Software links>Ubuntu Wiki (community-edited website) (http://wiki.ubuntu.com/)
Ubuntu and Free Software links>Make a Support Request to the Ubuntu Community (https://answers.launchpad.net/ubuntu/+addquestion)
Ubuntu and Free Software links>Debian (Ubuntu is based on Debian) (http://www.debian.org/)
Apps>The most popular self-hosted file share and collaboration platform (https://nextcloud.com/)
Apps>Open Source Password Management Solutions | Bitwarden (https://bitwarden.com/)
News>Welcome to LWN.net [LWN.net] (https://lwn.net/)
News>Services>NewsBlur (https://newsblur.com/)
Personal>Services>sleeplessbeastie's notes (https://blog.sleeplessbeastie.eu/)
Debian>Debian -- The Universal Operating System (https://www.debian.org/)
Debian>Planet Debian (https://planet.debian.org/)

Display and export bookmarks

Create nextcloud_bookmarks_print.sh shell script.

Shell script

#!/bin/sh
# Export Firefox bookmarks 

# path to the sqlite3 binary
sqlite_path=$(which sqlite3)

# sqlite3 parameters (define separator character)
sqlite_params="-separator ^"

# path to the places.sqlite database
bookmarks_database=$(ls ~/.mozilla/firefox/*.default/places.sqlite)

# SQL query 
sql_query="select p.title, p.url from moz_places as p where p.hidden=0 order by last_visit_date desc limit 10"

# root element
root_element_query="select id from moz_bookmarks where rtrim(guid,'_')='menu'"
root_element="$($sqlite_path $sqlite_params "$bookmarks_database""$root_element_query")"

# process bookmarks
process_bookmarks(){
  # SQL query - folders
  folder_id=$1
  folder_path=""
  while [ "$folder_id" != "2" ]; do
    sql_folder_query="select parent,title from moz_bookmarks where id=$folder_id and type=2 and (select count(*) from moz_bookmarks as b2 where b2.parent=moz_bookmarks.id)>0"

    sql_folder_result=$($sqlite_path $sqlite_params "$bookmarks_database""$sql_folder_query" )
    folder_id=$(echo $sql_folder_result | awk -F^ '{print $1}')
    folder_title=$(echo $sql_folder_result | awk -F^ '{print $2}')

    # special case for empty title
    if [ -z "$folder_path" ]; then
      folder_path="floccus:>$folder_title"
    else
      folder_path="${folder_path}>${folder_title}"
    fi
  done

  # escape special characters
  folder_path=$(echo $folder_path | sed -e "s/&/\&amp;/g" -e "s/\"/\&quot;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g")

  # SQL query - bookmarks
  sql_bookmarks_query="select b.title, p.url from moz_bookmarks as b left outer join moz_places as p on b.fk=p.id where b.type = 1 and p.hidden=0 and b.title not null and parent=$1"
  $sqlite_path $sqlite_params "$bookmarks_database""$sql_bookmarks_query" | while IFS=^ read title url; do
    # special case for empty title
    if [ -z "$title" ]; then
      title=$url
    fi

    # escape special characters
    title=$(echo $title | sed -e "s/&/\&amp;/g" -e "s/\"/\&quot;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g")
    url=$(echo $url | sed -e "s/&/\&amp;/g" -e "s/\"/\&quot;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g")

    e display url, title, path
    echo "<DT><A HREF=\"${url}\" TAGS=\"${folder_path}\">${title}</A>"
  done
}

# process the folders function
process_folders(){
  # execute only when there is an axactly one parameter
  if [ "$#" = 1 ]; then
    # SQL query - folders
    sql_folder_query="select id from moz_bookmarks where parent=$1 and type=2 and (select count(*) from moz_bookmarks as b2 where b2.parent=moz_bookmarks.id)>0"

    # process folders
    $sqlite_path $sqlite_params "$bookmarks_database""$sql_folder_query" | while IFS=^ read id; do
      # process folders inside
      process_folders $id
      
      # process bookmarks in current folder
      process_bookmarks $id 
    done
  fi
}


# header
echo "<!DOCTYPE NETSCAPE-Bookmark-file-1>"
echo "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">"
echo "<!-- This is an automatically generated file."
echo "It will be read and overwritten."
echo "Do Not Edit! -->"
echo "<TITLE>Bookmarks</TITLE>"
echo "<H1>Bookmarks</H1>"
echo "<DL><p>"

# process folders 
process_folders "$root_element"

# process bookmarks for root element
process_bookmarks "$root_element"

Usage

Export Firefox bookmarks to specified filename using floccus tags to preserve path.

$ nextcloud_bookmarks_print.sh | tee export.html
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<!-- This is an automatically generated file.
It will be read and overwritten.
Do Not Edit! -->
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>
<DT><A HREF="https://support.mozilla.org/pl/products/firefox" TAGS="floccus:>Mozilla Firefox">Pomoc</A>
<DT><A HREF="https://www.mozilla.org/pl/firefox/customize/" TAGS="floccus:>Mozilla Firefox">Dostosuj Firefoksa</A>
<DT><A HREF="https://www.mozilla.org/pl/contribute/" TAGS="floccus:>Mozilla Firefox">Dołącz do nas</A>
<DT><A HREF="https://www.mozilla.org/pl/about/" TAGS="floccus:>Mozilla Firefox">O Mozilli</A>
<DT><A HREF="http://www.ubuntu.com/" TAGS="floccus:>Ubuntu and Free Software links">Ubuntu</A>
<DT><A HREF="http://wiki.ubuntu.com/" TAGS="floccus:>Ubuntu and Free Software links">Ubuntu Wiki (community-edited website)</A>
<DT><A HREF="https://answers.launchpad.net/ubuntu/+addquestion" TAGS="floccus:>Ubuntu and Free Software links">Make a Support Request to the Ubuntu Community</A>
<DT><A HREF="http://www.debian.org/" TAGS="floccus:>Ubuntu and Free Software links">Debian (Ubuntu is based on Debian)</A>
<DT><A HREF="https://nextcloud.com/" TAGS="floccus:>Apps">The most popular self-hosted file share and collaboration platform</A>
<DT><A HREF="https://bitwarden.com/" TAGS="floccus:>Apps">Open Source Password Management Solutions | Bitwarden</A>
<DT><A HREF="https://lwn.net/" TAGS="floccus:>News">Welcome to LWN.net [LWN.net]</A>
<DT><A HREF="https://newsblur.com/" TAGS="floccus:>News>Services">NewsBlur</A>
<DT><A HREF="https://blog.sleeplessbeastie.eu/" TAGS="floccus:>Personal>Services">sleeplessbeastie's notes</A>
<DT><A HREF="https://www.debian.org/" TAGS="floccus:>Debian">Debian -- The Universal Operating System</A>
<DT><A HREF="https://planet.debian.org/" TAGS="floccus:>Debian">Planet Debian</A>

You can import created file to Nextcloud Bookmarks using web-interface or restore Nextcloud bookmarks shell script.

Use Nextcloud API to import bookmarks from Firefox

This is an automated solution, that will use API to import Firefox bookmarks directly to Nextcloud bookmarks application. Create firefox_bookmarks_add.sh shell script.

Shell script

#!/bin/sh
# Import Firefox bookmarks to Nexcloud
# https://blog.sleeplessbeastie.eu/

# path to the sqlite3 binary
sqlite_path=$(which sqlite3)

# sqlite3 parameters (define separator character)
sqlite_params="-separator ^"

# path to the places.sqlite database
bookmarks_database=$(ls ~/.mozilla/firefox/*.default/places.sqlite)

# SQL query 
sql_query="select p.title, p.url from moz_places as p where p.hidden=0 order by last_visit_date desc limit 10"

# root element
root_element_query="select id from moz_bookmarks where rtrim(guid,'_')='menu'"
root_element="$($sqlite_path $sqlite_params "$bookmarks_database""$root_element_query")"

# escape html
escape_html() {
  echo $* | perl -n -mHTML::Entities -e "print HTML::Entities::encode_entities_numeric(\$_,'<>&\"\'[](){}#@|%+')"
}

# process bookmarks
process_bookmarks(){
  # create folder path
  folder_id=$1
  folder_path=""
  while [ "$folder_id" != "2" ]; do
    sql_folder_query="select parent,title from moz_bookmarks where id=$folder_id and type=2 and (select count(*) from moz_bookmarks as b2 where b2.parent=moz_bookmarks.id)>0"

    sql_folder_result=$($sqlite_path $sqlite_params "$bookmarks_database""$sql_folder_query" )
    folder_id=$(echo $sql_folder_result | awk -F^ '{print $1}')
    folder_title=$(echo $sql_folder_result | awk -F^ '{print $2}')

    # special case for empty title
    if [ -z "$folder_path" ]; then
      folder_path="floccus:>$folder_title"
    else
      folder_path="${folder_path}>${folder_title}"
    fi
  done

  # process bookmarks
  sql_bookmarks_query="select b.title, p.url from moz_bookmarks as b left outer join moz_places as p on b.fk=p.id where b.type = 1 and p.hidden=0 and b.title not null and parent=$1"
  $sqlite_path $sqlite_params "$bookmarks_database""$sql_bookmarks_query" | while IFS=^ read ff_title ff_url; do
    # check if url is already stored before adding it
    found_bookmark="0"
    continue_pagination="1"
    page=0
    while [ "${continue_pagination}" -eq "1" ]; do
      urls=$(curl --silent -X GET --user "${param_username}:${param_password}" \
                                  --header "Accept: application/json" \
                  "${param_nextcloud_address}/index.php/apps/bookmarks/public/rest/v2/bookmark?page=${page}&search\[\]=$(escape_html $ff_url)" | \
             jq -r '.data[].url')
      if [ -z "${urls}" ]; then
        continue_pagination="0"
      else
        for url in $urls; do
          if [ "$ff_url" == "$url" ]; then
            found_bookmark="1"
            break 
          fi
        done
      fi

      if [ "${found_bookmark}" -eq "0" ]; then
        status=$(curl --silent -X POST --user "${param_username}:${param_password}" \
                                       --data-urlencode "url=$ff_url" \
                                       --data-urlencode "title=$ff_title" \
                                       --data-urlencode "item[tags][]=$folder_path" \
                      "${param_nextcloud_address}/index.php/apps/bookmarks/public/rest/v2/bookmark" | \
                 jq -r 'select(.status != "success") | .status')
        if [ -n "${status}" ]; then
          echo "Skipped Nextcloud bookmark url \"${ff_url}\" with title \"${ff_title}\" and tag \"${folder_path}\"."
        else
          echo "Added Nextcloud bookmark url \"${ff_url}\" with title \"${ff_title}\" and tag \"${folder_path}\"."
        fi
        continue_pagination="0"
        page="0"
      else
        page=$(expr $page + 1)
      fi
    done
  done
}

# process the folders function
process_folders(){
  # execute only when there is an exactly one parameter
  if [ "$#" = 1 ]; then
    # SQL query - folders
    sql_folder_query="select id from moz_bookmarks where parent=$1 and type=2 and (select count(*) from moz_bookmarks as b2 where b2.parent=moz_bookmarks.id)>0"

    # process folders
    $sqlite_path $sqlite_params "$bookmarks_database""$sql_folder_query" | while IFS=^ read id; do
      # process folders inside
      process_folders $id
      
      # process bookmarks in current folder
      process_bookmarks $id 
    done
  fi
}

# usage info
usage(){
  echo "Usage:"
  echo "  $0 -r nextcloud_url -u username -p passsword"
  echo ""
  echo "Parameters:"
  echo "  -r nextcloud_url   : set Nextcloud URL (required)"
  echo "  -u username        : set username (required)"
  echo "  -p password        : set password (required)"
  echo ""
}

# parse parameters
while getopts "r:u:p:" option; do
  case $option in
    "r")
      param_nextcloud_address="${OPTARG}"
      param_nextcloud_address_defined=true
      ;;
    "u")
      param_username="${OPTARG}"
      param_username_defined=true
      ;;
    "p")
      param_password="${OPTARG}"
      param_password_defined=true
      ;;
    \?|:|*)
      usage
      exit
      ;;
  esac
done

if [ "${param_nextcloud_address_defined}" = true ] && \
   [ "${param_username_defined}"          = true ] && \
   [ "${param_password_defined}"          = true ]; then

  # process folders 
  process_folders "$root_element"

  # process bookmarks for root element
  process_bookmarks "$root_element"
else
  usage
fi

Usage

Display usage information.

$ firefox_bookmarks_add.sh 
Usage:
  firefox_bookmarks_add.sh -r nextcloud_url -u username -p passsword

Parameters:
  -r nextcloud_url   : set Nextcloud URL (required)
  -u username        : set username (required)
  -p password        : set password (required)

Import Firefox bookmarks.

$ firefox_bookmarks_add.sh -r https://cloud.example.org/ -u milosz -p Mjdu3-kDnru-4UksA-fYs0w
Added Nextcloud bookmark url "https://support.mozilla.org/pl/products/firefox" with title "Pomoc" and tag "floccus:>Mozilla Firefox".
Added Nextcloud bookmark url "https://www.mozilla.org/pl/firefox/customize/" with title "Dostosuj Firefoksa" and tag "floccus:>Mozilla Firefox".
Added Nextcloud bookmark url "https://www.mozilla.org/pl/contribute/" with title "Dołącz do nas" and tag "floccus:>Mozilla Firefox".
Added Nextcloud bookmark url "https://www.mozilla.org/pl/about/" with title "O Mozilli" and tag "floccus:>Mozilla Firefox".
Added Nextcloud bookmark url "http://www.ubuntu.com/" with title "Ubuntu" and tag "floccus:>Ubuntu and Free Software links".
Added Nextcloud bookmark url "http://wiki.ubuntu.com/" with title "Ubuntu Wiki (community-edited website)" and tag "floccus:>Ubuntu and Free Software links".
Added Nextcloud bookmark url "https://answers.launchpad.net/ubuntu/+addquestion" with title "Make a Support Request to the Ubuntu Community" and tag "floccus:>Ubuntu and Free Software links".
Added Nextcloud bookmark url "http://www.debian.org/" with title "Debian (Ubuntu is based on Debian)" and tag "floccus:>Ubuntu and Free Software links".
Added Nextcloud bookmark url "https://nextcloud.com/" with title "The most popular self-hosted file share and collaboration platform" and tag "floccus:>Apps".
Added Nextcloud bookmark url "https://bitwarden.com/" with title "Open Source Password Management Solutions | Bitwarden" and tag "floccus:>Apps".
Added Nextcloud bookmark url "https://lwn.net/" with title "Welcome to LWN.net [LWN.net]" and tag "floccus:>News".
Added Nextcloud bookmark url "https://newsblur.com/" with title "NewsBlur" and tag "floccus:>News>Services".
Added Nextcloud bookmark url "https://blog.sleeplessbeastie.eu/" with title "sleeplessbeastie's notes" and tag "floccus:>Personal>Services".
Added Nextcloud bookmark url "https://www.debian.org/" with title "Debian -- The Universal Operating System" and tag "floccus:>Debian".
Added Nextcloud bookmark url "https://planet.debian.org/" with title "Planet Debian" and tag "floccus:>Debian".

Additional notes

Download source code, Firefox bookmarks exported in json and html format are included too.

Remember to backup Nextcloud bookmarks.


How to create simplest possible iptables firewall

$
0
0

Create simplest possible iptables firewall with quite relaxed rules that will allow all outgoing traffic, incoming icmp packets and ssh connections on eth0 interface.

# Flush INPUT/OUTPUT/FORWARD chains
iptables -F INPUT
iptables -F OUTPUT
iptables -F FORWARD

# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Pass everything on loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept incoming packets for established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Accept incoming ICMP
iptables -A INPUT -p icmp -j ACCEPT

# Accept incoming SSH on eth0 interface
iptables -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT

# Accept connections
iptables -P OUTPUT ACCEPT

# Drop everything else on INPUT/FORWARD
iptables -P INPUT   DROP
iptables -P FORWARD DROP

List all firewall rules to verify that executed commands are applied as desired.

$ sudo iptables -L -v -n
Chain INPUT (policy DROP 1 packets, 32 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
  252 19645 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    60 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 188 packets, 30167 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      lo      0.0.0.0/0            0.0.0.0/0   

How to serve files from memory with a fallback using nginx

$
0
0

Serve files from memory with a fallback using nginx HTTP server.

The goal is to serve files from memory using tmpfs filesystem mounted at /var/www/tmpfs/ directory and use a fallback /var/www/html/ directory in case the requested file or direcory is missing.

Install nginx HTTP server.

$ sudo apt-get install nginx-full

Create directory for tmpfs.

$ sudo mkdir -p /var/www/tmpfs

Mount tmpfs filesystem.

$ sudo mount -t tmpfs -o rw,nodev,nosuid,noexec,size=128M tmpfs /var/www/tmpfs/

Ensure that it will be mounted on boot.

$ echo "tmpfs /var/www/tmpfs tmpfs rw,nodev,nosuid,noexec,size=128M 0 0" | sudo tee -a /etc/fstab 

Disable default virtual host configuration.

$ sudo unlink /etc/nginx/sites-enabled/default

Prepare virtual host configuration.

$ cat << EOF | sudo tee /etc/nginx/sites-available/default-tmpfs-with-fallback
server {
  listen 80 default_server;
  listen [::]:80 default_server;
  index index.html index.htm index.nginx-debian.html;

  server_name _;

  location / {
    root /var/www/tmpfs/;
    try_files \$uri \$uri/ @fallback;
  }

  location @fallback  {
    root /var/www/html/;
    try_files \$uri \$uri/ =404;
  }
}
EOF
server {
  listen 80 default_server;
  listen [::]:80 default_server;
  index index.html index.htm index.nginx-debian.html;

  server_name _;

  location / {
    root /var/www/tmpfs/;
    try_files $uri $uri/ @fallback;
  }

  location @fallback  {
    root /var/www/html/;
    try_files $uri $uri/ =404;
  }
}

Enable this virtual host.

$ sudo ln -s /etc/nginx/sites-available/default-tmpfs-with-fallback /etc/nginx/sites-enabled/

Verify configuration syntax.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload HTTP server configuration.

$ sudo systemctl reload nginx

Install rsync a fast, versatile, remote (and local) file-copying tool.

$ sudo apt-get install rsync

Copy website to tmpfs filesystem.

$ sudo rsync --archive /var/www/html/ /var/www/tmpfs/

Done.

How to store LeaseWeb data traffic in OpenTSDB time series database

$
0
0

Store LeaseWeb data traffic in OpenTSDB time series database using Python sript, LeaseWeb API v2 and Python client to OpenTSDB.

OpenTSDB - LeaseWeb data traffic
OpenTSDB - LeaseWeb data traffic

Requirements

I will use Python3 and basic Python modules and potsdb package, which can be installed using pip3 a Python package installer.

LeaseWeb API is disabled by default, enable it using Customer Portal then add your IP address to the IP Whitelist for security reasons.

Python script

Use the following script to display and print information gathered using LeaseWeb API.

Use api_keys variable to define each location and assigned API key.

#!/usr/bin/python3
# Log LeaseWeb data traffic to OpenTSDB
# https://blog.sleeplessebastie.eu/2018/05/18/how-to-store-leaseweb-data-traffic-in-opentsdb-time-series-database/

# http
import http.client

# json
import json

# opentsdb
import potsdb

# date
import datetime

# define api keys
api_keys={"NL":"8517f379-aa60-5c1e-53ef-55203cab8c81d", "DE":"c406d92f-3603-f477-6086-7e3a1953ba2dc", "US":"16986a34-4148-7141-142f-b9c998f8ca878", "SG": "a12da181-029e-7a5f-cf8e-c3c70ea717db1"}

# opentsdb server/port
opentsdb_server="192.0.2.100"
opentsdb_port=4242
opentsdb_metric_name="leaseweb.datatraffic"
opentsdb_metric_tag="hostname"

# current datetime
current_datetime = datetime.datetime.utcnow()

for key in api_keys:
  try:
    # get server list
    https_connection = http.client.HTTPSConnection("api.leaseweb.com")
    https_headers = { 'x-lsw-auth': api_keys[key]}
    https_connection.request("GET", "/bareMetals/v2/servers", headers=https_headers)
    https_response = https_connection.getresponse()

    https_data = https_response.read()
    json_servers = json.loads(https_data.decode("utf-8"))

    # connect to opentsdb
    metrics = potsdb.Client(opentsdb_server, port=opentsdb_port,check_host=False)
  except:
    continue

  if not 'servers' in json_servers:
    print('LeaseWeb', key, 'returned no data')
    continue

  # for each server
  for bareMetal in json_servers['servers']:
    try:
      # get id and name
      bareMetalId = bareMetal['id']
      serverName  = bareMetal['contract']['internalReference']

      # get datatraffic
      https_connection.request('GET', '/bareMetals/v2/servers/' + bareMetalId + '/metrics/datatraffic?from=' + str(current_datetime.year) + '-' + str(current_datetime.month).rjust(2,'0') + '-' + '01' + 'T00:00:00Z' + '&to=' + str(current_datetime.year) + '-' + str(current_datetime.month).rjust(2,'0') + '-' + str(current_datetime.day) + 'T' + str(current_datetime.hour).rjust(2,'0')+':00:00Z&aggregation=SUM', headers=https_headers)
      https_response = https_connection.getresponse()
      https_data = https_response.read()

      # load json
      json_datatraffic = json.loads(https_data.decode("utf-8"))

      # calculate total datatrraffic
      datatraffic=int(json_datatraffic['metrics']['DOWN_PUBLIC']['values'][0]['value']) + int(json_datatraffic['metrics']['UP_PUBLIC']['values'][0]['value'])

      # push to opentsdb
      metrics.log(opentsdb_metric_name,datatraffic, **{opentsdb_metric_tag: serverName})
    except:
      continue

  # on exit
  metrics.wait()
  https_connection.close()

The above graph is showing the following data.

LeaseWeb DE
 * PQJR001
   This month data traffic on public interface: 
     4.59 GB in 1.52 GB out 6.11 GB total
 * PQJR002
   This month data traffic on public interface: 
     9.58 GB in 20.63 GB out 30.2 GB total

Check out how to display LeaseWeb data traffic using API v2 blog post to display data traffic using similar format.

Additional information

How to integrate lightline status line plugin for Vim with Solarized theme

$
0
0

I am using lightline status line with Solarized color scheme inside Vim the ubiquitous text editor. It looks great, but there is a small issue that needs to be solved - change status line color scheme alongside main background.

Light/Dark Solarized theme with lightline status line

Prerequisites

Ensure that terminal is set to Solarized color scheme.

Terminal color scheme settings

This tutorial requires at least most recent Vim 7.4 (probably the lowest supported version is 7.4.1528 in this case) as it provides native third-party package loading, but you can use older version with pathogen, vundle or vim-plug Vim plugin manager.

Vim version

On the other hand, minipac is a minimal package manager for Vim 8 that takes adavantage of new features.

Install plugins

Create directory that will be used to load main color scheme and status line plugin during application startup.

$ mkdir -p ~/.vim/pack/interface/start/

Download Solarized color scheme.

$ git clone https://github.com/altercation/vim-colors-solarized ~/.vim/pack/interface/start/vim-colors-solarized

Download lightline status line plugin.

$ git clone https://github.com/itchyny/lightline.vim ~/.vim/pack/interface/start/lightline.vim

Configure Vim

Create basic ~/.vimrc file with additional ToggleSolarizedTheme() function mapped to F12 key that will change background color and indirectly main color scheme, then update satus line to reflect changes. It is based on ToggleBG funcion from Solarized theme.

" make Vim behave in a more useful way
set nocompatible

" define number of colors
set t_Co=256

" define initial background (light/dark)
set background=dark

" define color scheme
colorscheme solarized

" always display status line
set laststatus=2

" do not show mode
set noshowmode

" define lightline configuration
let g:lightline = {
      \ 'colorscheme': 'solarized',
      \ }

" define function to toggle solarized theme 
" change background and update lightline color scheme
function! ToggleSolarizedTheme()
  let &background = ( &background == "dark"? "light" : "dark" )
  if exists("g:lightline")
    runtime autoload/lightline/colorscheme/solarized.vim
    call lightline#colorscheme()
  endif
endfunction

" map F12 to ToggleSolarizedTheme() function
map <F12> :call ToggleSolarizedTheme()<CR>

Reload configuration file or restart Vim. Use F12 key to swap between dark/light color scheme.

How to redirect every request to defined domain to particular location

$
0
0

Recently, I have moved my personal source code to GitLab. It rendered self-hosted git repository at repository.sleeplessbeastie.eu obsolete, so I have created simple redirection on HAProxy load balanced to cover this specific case.

Permanently redirect every request to repository.sleeplessbeastie.eu domain to https://gitlab.com/milosz.galazka location.

acl is-repository hdr_dom(host) -i repository.sleeplessbeastie.eu
http-request redirect location https://gitlab.com/milosz.galazka code 301 if is-repository

Verify that it works as expected.

$ curl --verbose --silent --location --output /dev/null https://repository.sleeplessbeastie.eu
* Rebuilt URL to: https://repository.sleeplessbeastie.eu/
*   Trying 84.16.240.28...
* TCP_NODELAY set
* Connected to repository.sleeplessbeastie.eu (84.16.240.28) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: repository.sleeplessbeastie.eu
* Server certificate: Let's Encrypt Authority X3
* Server certificate: DST Root CA X3
> GET / HTTP/1.1
> Host: repository.sleeplessbeastie.eu
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-length: 0
< Location: https://gitlab.com/milosz.galazka
<
* Connection #0 to host repository.sleeplessbeastie.eu left intact
* Issue another request to this URL: 'https://gitlab.com/milosz.galazka'
*   Trying 52.167.219.168...
* TCP_NODELAY set
* Connected to gitlab.com (52.167.219.168) port 443 (#1)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate: gitlab.com
* Server certificate: COMODO RSA Domain Validation Secure Server CA
* Server certificate: COMODO RSA Certification Authority
> GET /milosz.galazka HTTP/1.1
> Host: gitlab.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Wed, 24 Jan 2018 01:55:45 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Cache-Control: max-age=0, private, must-revalidate
< Etag: W/"dd7648465b5764790dc2a7f0f11be658"< Set-Cookie: _gitlab_session=341990252dd0180f509f0ff3bf2d485c; path=/; secure; HttpOnly
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-Request-Id: e14e3c77-1c94-49a4-8e60-b564f0fab5fd
< X-Runtime: 0.072131
< X-Ua-Compatible: IE=edge
< X-Xss-Protection: 1; mode=block
< Strict-Transport-Security: max-age=31536000
< Content-Security-Policy: object-src 'none'; script-src 'self''unsafe-inline''unsafe-eval' https://assets.gitlab-static.net https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; style-src 'self''unsafe-inline' https://assets.gitlab-static.net; img-src * data: blob:; frame-src 'self' https://www.google.com/recaptcha/; frame-ancestors 'self'; connect-src 'self' https://assets.gitlab-static.net wss://gitlab.com https://sentry.gitlap.com https://customers.gitlab.com; report-uri https://sentry-infra.gitlap.com/api/3/csp-report/?sentry_key=a664fdde83424b43a991f25fa7c78987
<
{ [14792 bytes data]
* Connection #1 to host gitlab.com left intact

How to execute additional commands during system startup using cron

$
0
0

Use cron service to execute additional commands as specified user using cron during system startup.

Use @reboot time specification to execute command once after reboot.

Root can use cron service to set noop IO scheduler for sda device at boot.

$ (sudo crontab -u root -l 2>/dev/null; echo -e "# io scheduler\n@reboot (/bin/echo noop | /usr/bin/tee /sys/block/sda/queue/scheduler)") | sudo crontab -u root -
$ sudo crontab -l
# use noop io scheduler for sda
@reboot (/bin/echo noop | /usr/bin/tee /sys/block/sda/queue/scheduler)

User can use cron service to clear history at boot.

$ (crontab -u $(whoami) -l 2>/dev/null; echo -e "# clean history on reboot\n@reboot (: | /usr/bin/tee ~/.bash_history)") | crontab -u $(whoami) -
$ crontab -l
# clean history on reboot
@reboot (: | /usr/bin/tee ~/.bash_history)

There are many more possibilities as you are not limited to these two simple usage scenarios.

How to determine process execution time

$
0
0

Use GNU Bourne-Again SHell builtin or standalone time command to determine process execution time.

GNU Bourne-Again SHell builtin time command

Use Bash builtin time command to get real execution time.

$ time rsync --dry-run -Havip --delete /home/milosz/docs/ /backup/notebook-docs/
.d..t...... project-x/.git/
>f.st...... project-x/.git/COMMIT_EDITMSG
>f.st.o.... project-x/.git/index
>f.st...... project-x/.git/logs/HEAD
>f.st...... project-x/.git/logs/refs/heads/master
>f.st...... project-x/.git/logs/refs/remotes/origin/master
.d..t...... project-x/.git/modules/core/
.d..t...... project-x/.git/objects/06/
>f+++++++++ project-x/.git/objects/05/253ae0470d4b948c40d83df9171b0f282bea1d
.d..t...... project-x/.git/objects/35/
>f+++++++++ project-x/.git/objects/35/a62b543d2865dadc0dafebd2bf2d8bf78eff6d
.d..t...... project-x/.git/objects/36/
>f+++++++++ project-x/.git/objects/36/dac5f479c331e66bb7297c3253dc3c3f6724e0
.d..t...... project-x/.git/objects/3a/
>f+++++++++ project-x/.git/objects/3a/1f44736cd56a68e8db2e29aa14cba5379b2e85
.d..t...... project-x/.git/objects/93/
>f+++++++++ project-x/.git/objects/93/f049ceae4d2ec503143a534ee3754b4dd1cb46
.d..t...... project-x/.git/objects/a4/
>f+++++++++ project-x/.git/objects/a4/e9fb24ab0415c57cd2fa3751dfa32efaecd9a9
.d..t...... project-x/.git/objects/d9/
>f+++++++++ project-x/.git/objects/d9/56475059256c53b1c8bcf22442453d7de02c7e
.d..t...... project-x/.git/refs/heads/
>f..t.o.... project-x/.git/refs/heads/master
.d..t...... project-x/.git/refs/remotes/origin/
>f..t.o.... project-x/.git/refs/remotes/origin/master
.d..t...... project-x/.git/refs/tags/
>f+++++++++ project-x/.git/refs/tags/1.1

real    0m29.795s
user    0m12.692s
sys     0m29.624s

You can define TIMEFORMAT variable to modify output format.

$ export TIMEFORMAT="Execution time: %0lR (%0R seconds)"
$ time rsync --dry-run -Havip --delete /home/milosz/docs/ /backup/notebook-docs/
[...]
Execution time: 0m31s (31 seconds)

Inspect bash manual page for detailed TIMEFORMAT parameter description.

Standalone GNU time command

Install dedicated GNU time program for measuring CPU resource usage.

$ sudo apt-get install time
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  time
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 31.6 kB of archives.
After this operation, 84.0 kB of additional disk space will be used.
Get:1 http://deb.debian.org/debian stretch/main amd64 time amd64 1.7-25.1+b1 [31.6 kB]
Fetched 31.6 kB in 0s (1760 kB/s)
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package time.
(Reading database ... 8412 files and directories currently installed.)
Preparing to unpack .../time_1.7-25.1+b1_amd64.deb ...
Unpacking time (1.7-25.1+b1) ...
Setting up time (1.7-25.1+b1) ...

Display portable time format string for conformance with POSIX standard 1003.2.

$ /usr/bin/time --portability rsync --dry-run -Havip --delete /home/milosz/docs/ /backup/notebook-docs/
.d..t...... project-x/.git/
>f.st...... project-x/.git/COMMIT_EDITMSG
>f.st.o.... project-x/.git/index
>f.st...... project-x/.git/logs/HEAD
>f.st...... project-x/.git/logs/refs/heads/master
>f.st...... project-x/.git/logs/refs/remotes/origin/master
.d..t...... project-x/.git/modules/core/
.d..t...... project-x/.git/objects/06/
>f+++++++++ project-x/.git/objects/05/253ae0470d4b948c40d83df9171b0f282bea1d
.d..t...... project-x/.git/objects/35/
>f+++++++++ project-x/.git/objects/35/a62b543d2865dadc0dafebd2bf2d8bf78eff6d
.d..t...... project-x/.git/objects/36/
>f+++++++++ project-x/.git/objects/36/dac5f479c331e66bb7297c3253dc3c3f6724e0
.d..t...... project-x/.git/objects/3a/
>f+++++++++ project-x/.git/objects/3a/1f44736cd56a68e8db2e29aa14cba5379b2e85
.d..t...... project-x/.git/objects/93/
>f+++++++++ project-x/.git/objects/93/f049ceae4d2ec503143a534ee3754b4dd1cb46
.d..t...... project-x/.git/objects/a4/
>f+++++++++ project-x/.git/objects/a4/e9fb24ab0415c57cd2fa3751dfa32efaecd9a9
.d..t...... project-x/.git/objects/d9/
>f+++++++++ project-x/.git/objects/d9/56475059256c53b1c8bcf22442453d7de02c7e
.d..t...... project-x/.git/refs/heads/
>f..t.o.... project-x/.git/refs/heads/master
.d..t...... project-x/.git/refs/remotes/origin/
>f..t.o.... project-x/.git/refs/remotes/origin/master
.d..t...... project-x/.git/refs/tags/
>f+++++++++ project-x/.git/refs/tags/1.1

real 28.88
user 12.56
sys 28.76

Display custom time format defined using format option.

$ /usr/bin/time --format="Execution time: %E (%e seconds)" rsync --dry-run -Havip --delete /home/milosz/docs/ /backup/notebook-docs/
[...]
Execution time: 0:29.29 (29.29 seconds)

Display custom time format defined using TIME variable.

$ export TIME="Execution time: %E (%e seconds)"
$ /usr/bin/time rsync --dry-run -Havip --delete /home/milosz/docs /backup/notebook-docs
[...]
Execution time: 0:29.99 (29.99 seconds)

Execute command five times in a row and append custom time format defined using format option to the specified file.

$ for i in $(seq 1 5); do \
  /usr/bin/time --append --output rsync-time.log \
                --format="Execution time: %E (%e seconds)" \
                rsync --dry-run -Havip --delete /home/milosz/docs/ /backup/notebook-docs/; \
  done
[...]
$ cat rsync-time.log
Execution time: 0:27.50 (27.50 seconds)
Execution time: 0:28.57 (28.57 seconds)
Execution time: 0:29.31 (29.31 seconds)
Execution time: 0:29.64 (29.64 seconds)
Execution time: 0:30.44 (30.44 seconds)

Inspect time manual page for detailed output format description.


How to detect and log changes in the list of mounted filesystems

$
0
0

Detect and log changes in the list of mounted filesystems (/proc/self/mountinfo file).

Create shell script that will poll for changes in the list of mounted filesystems and log these using logger utility.

$ cat << EOF | sudo tee /usr/sbin/mount-monit.sh
#!/bin/bash
# poll for changes in mounted filesystems and log these

SYSTEMD_PIDS=\$(ps -o pid= -C systemd)
SYSTEMD_IS_PARENT=0
MY_PARENT=\$(ps -o ppid= -p \$\$)

for PID in \$SYSTEMD_PIDS; do
  if [ "\$PID" -eq "\$MY_PARENT" ]; then
    SYSTEMD_IS_PARENT=1
  fi
done

# ensure that this script is executed by systemd
if [ "\$SYSTEMD_IS_PARENT" -eq "0" ]; then
  exit 1
fi

findmnt --poll | \\
  while read -r line; do \\
    echo \$line | \\
    awk '\$1 ~ /^(u|re)?mount|move/ {print \$0}' | \\
    logger --priority "local0.notice" --tag "mount-monitor"; \\
  done
EOF
#!/bin/bash
# poll for changes in mounted filesystems and log these

SYSTEMD_PIDS=$(ps -o pid= -C systemd)
SYSTEMD_IS_PARENT=0
MY_PARENT=$(ps -o ppid= -p $$)

for PID in $SYSTEMD_PIDS; do
  if [ "$PID" -eq "$MY_PARENT" ]; then
    SYSTEMD_IS_PARENT=1
  fi
done

# ensure that this script is executed by systemd
if [ "$SYSTEMD_IS_PARENT" -eq "0" ]; then
  exit 1
fi

findmnt --poll | \
  while read -r line; do \
    echo $line | \
    awk '$1 ~ /^(u|re)?mount|move/ {print $0}' | \
    logger --priority "local0.notice" --tag "mount-monit"; \
  done

Set executable bit.

$ sudo chmod +x /usr/sbin/mount-monit.sh

Create systemd service file.

$ cat << EOF | sudo tee /etc/systemd/system/mount-monit.service 
[Unit]
Description=Mount monitor

[Service]
Type=simple
Restart=always
RestartSec=30
ExecStart=/usr/sbin/mount-monit.sh

[Install]
WantedBy=multi-user.target
EOF
[Unit]
Description=Mount monitor

[Service]
Type=simple
Restart=always
RestartSec=30
ExecStart=/usr/sbin/mount-monit.sh

[Install]
WantedBy=multi-user.target

Reload systemd manager configuration.

$ sudo systemctl daemon-reload

Enable service at boot time.

$ sudo systemctl enable mount-monit.service

Start service.

$ sudo systemctl start mount-monit.service

Display service status.

$ sudo systemctl status mount-monit.service
● mount-monit.service - Mount monitor
   Loaded: loaded (/etc/systemd/system/mount-monit.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2017-12-10 16:31:50 CST; 7s ago
 Main PID: 10882 (mount-monit.sh)
    Tasks: 3 (limit: 4915)
   CGroup: /system.slice/mount-monit.service
           ├─10882 /bin/bash /usr/sbin/mount-monit.sh
           ├─10885 findmnt --poll
           └─10886 /bin/bash /usr/sbin/mount-monit.sh

Dec 10 16:31:50 debian systemd[1]: Started Mount monitor.

Sample mount operations and their corresponding log entries.

$ sudo mount -t tmpfs none /var/www/assets/
$ sudo mount -o remount,size=128M /var/www/assets/
$ sudo umount /var/www/assets 
Dec 10 16:53:23 debian mount-monitor: mount /var/www/assets none tmpfs rw,relatime
Dec 10 16:53:37 debian mount-monitor: remount /var/www/assets none tmpfs rw,relatime,size=131072k
Dec 10 16:56:34 debian mount-monitor: umount /var/www/assets none tmpfs rw,relatime,size=131072k

How to use HTTP host header to choose HAProxy backend

$
0
0

Dynamically choose HAProxy backend depending on the HTTP host header, Lua programming language and environment variable.

HAProxy version.

$ haproxy -v
HA-Proxy version 1.7.5-2 2017/05/17
Copyright 2000-2017 Willy Tarreau <willy@haproxy.org>

Default HAProxy configuration.

global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	stats socket /run/haproxy/admin.sock mode 660 level admin
	stats timeout 30s
	user haproxy
	group haproxy
	daemon

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	# Default ciphers to use on SSL-enabled listening sockets.
	# For more information, see ciphers(1SSL). This list is from:
	#  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
	# An alternative list with additional directives can be obtained from
	#  https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
	ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
	ssl-default-bind-options no-sslv3

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

Inspect systemd service file to learn that you can use /etc/default/haproxy file to define additional environment variables.

$ cat /lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
Documentation=man:haproxy(1)
Documentation=file:/usr/share/doc/haproxy/configuration.txt.gz
After=network.target syslog.service
Wants=syslog.service

[Service]
Environment="CONFIG=/etc/haproxy/haproxy.cfg""PIDFILE=/run/haproxy.pid"
EnvironmentFile=-/etc/default/haproxy
ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS
ExecStart=/usr/sbin/haproxy-systemd-wrapper -f $CONFIG -p $PIDFILE $EXTRAOPTS
ExecReload=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always

[Install]
WantedBy=multi-user.target

Use HAPROXY_SERVER_NAME variable to define fully qualified domain name of the HAProxy server. Unfortunatelly, you cannot use $(hostname --fqdn) command to set it on service start.

$ cat << EOF | sudo tee -a /etc/default/haproxy

# pass server name to haproxy
HAPROXY_SERVER_NAME="example.org"
EOF

# pass server name to haproxy
HAPROXY_SERVER_NAME="example.org"
$ cat /etc/default/haproxy 
# Defaults file for HAProxy
#
# This is sourced by both, the initscript and the systemd unit file, so do not
# treat it as a shell script fragment.

# Change the config file location if needed
#CONFIG="/etc/haproxy/haproxy.cfg"

# Add extra flags here, see haproxy(1) for a few options
#EXTRAOPTS="-de -m 16"

# pass server name to haproxy
HAPROXY_SERVER_NAME="example.org"

Define Lua function to get hostname defined in HTTP host header.

$ cat << | sudo tee /etc/haproxy/destination_hostname.lua
core.register_fetches("destination_hostname", function(txn)
  local hostname = txn.sf:req_fhdr("host"):lower()
  return hostname
end)
EOF
core.register_fetches("destination_hostname", function(txn)
  local hostname = txn.sf:req_fhdr("host"):lower()
  return hostname
end)

Load Lua script inside global section.

global
  [...]
  lua-load /etc/haproxy/destination_hostname.lua

Define frontend and backends to use default backend for HAProxy address (this is why we need HAPROXY_SERVER_NAME environment variable), example.com and example.net backends for their respective addresses, nonexistent backend for every other ddress.

frontend development-frontend
  bind :80
  #bind :443 ssl crt /etc/ssl/cert/

  option httplog

  option forwardfor except 127.0.0.1
  option forwardfor header X-Real-IP

  #redirect scheme https code 301 if !{ ssl_fc }

  acl is-host-itself lua.destination_hostname() "${HAPROXY_SERVER_NAME}"
  http-request set-var(txn.destination_hostname) lua.destination_hostname()

  use_backend default if is-host-itself
  use_backend %[var(txn.destination_hostname)]
  default_backend nonexistent  


backend default
  server default 10.66.91.125:80

backend example.com
  server default 10.66.91.52:80

backend example.net
  server default 10.66.91.53:80

backend nonexistent
  server default 10.66.91.50:80

Sample log output for regular query to HAProxy hostname (default backend).

$ curl http://example.org
Jan 21 17:05:21 example haproxy[7858]: 10.66.91.165:32856 [21/Jan/2018:17:05:21.581] development-frontend default/default 0/0/0/4/5 200 9386 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

Sample log output for regular query to the hosted example.com domain (example.com backend).

$ curl http://example.com
Jan 21 17:06:09 example haproxy[7858]: 10.66.91.165:32868 [21/Jan/2018:17:06:09.845] development-frontend example.com/default 0/0/0/1/1 301 805 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

Sample log output for regular query to the hosted example.net domain (example.net backend).

$ curl http://example.net
Jan 21 17:06:12 example haproxy[7858]: 10.66.91.165:32874 [21/Jan/2018:17:06:12.170] development-frontend example.net/default 0/0/0/123/123 200 29158 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

Sample log output for regular query to the not hosted example.co.uk domain (nonexistent backend).

$ curl http://example.co.uk
Jan 21 17:06:46 example haproxy[7858]: 10.66.91.165:32880 [21/Jan/2018:17:06:46.662] development-frontend nonexistent/default 0/0/0/0/0 200 11174 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

How to display upcoming events in Nextcloud calendar using text-based terminal emulator

$
0
0

Display upcoming events in Nextcloud calendar using text-based terminal emulator because it is fun and challenging.

This article is quite lengthy as it includes XML output and parsed data for easier understanding.

Initial assumptions

Today's date is Sun Dec 17 10:07:09 CET 2017.

The start date will be defined as 20171217T000000 and end date as 20171218T000000.

Nextcloud DAV resource is available at https://cloud.example.org/remote.php/dav/ using username crono and application password zabie.

Install required software

Install curl utility to request and download Nextcloud calendar events.

$ sudo apt-get install curl

Install xmlstarlet utility to parse downloaded XML documents.

$ sudo apt-get install xmlstarlet

Create application password

Define individual password for an application in NextCloud server, so it could be revoked later. Do not use your password.

Command-line operations

Get path for the user's principal resource on the server.

$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:current-user-principal />
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/</d:href>
    <d:propstat>
      <d:prop>
        <d:current-user-principal>
          <d:href>/remote.php/dav/principals/users/crono/</d:href>
        </d:current-user-principal>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:current-user-principal />
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/d:current-user-principal/d:href' -n
/remote.php/dav/principals/users/crono/

Get path that contains calendar collections owned by the user using user's principal resource address obtained in the previous step.

$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">                                                                                                          
                 <d:prop>    
                   <c:calendar-home-set />                                        
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/principals/users/crono/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/principals/users/crono/</d:href>
    <d:propstat>
      <d:prop>
        <cal:calendar-home-set>
          <d:href>/remote.php/dav/calendars/crono/</d:href>
        </cal:calendar-home-set>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop>
                   <c:calendar-home-set />
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/principals/users/crono/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-home-set/d:href' -n
/remote.php/dav/calendars/crono/

Get calendar paths for given collection.

$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 1' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://o
wncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname/>
      </d:prop>
      <d:status>HTTP/1.1 404 Not Found</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Personal</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/debian/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Debian</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/work/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Work</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Projects</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/inbox/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname/>
      </d:prop>
      <d:status>HTTP/1.1 404 Not Found</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/outbox/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname/>
      </d:prop>
      <d:status>HTTP/1.1 404 Not Found</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 1' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/ | \
  xmlstarlet sel -t -m 'd:multistatus/d:response' -i  "string-length(d:propstat/d:prop/d:displayname)" -i "d:propstat/d:status='HTTP/1.1 200 OK'" -v "d:href" -n
/remote.php/dav/calendars/crono/personal/
/remote.php/dav/calendars/crono/debian/
/remote.php/dav/calendars/crono/work/
/remote.php/dav/calendars/crono/projects/

Get calendar name.

$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Personal</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/d:displayname' -n
Personal

Get event types stored in this calendar e.g. VTODO, VEVENT.

$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:cal="urn:ietf:params:xml:ns:caldav">
                 <d:prop>
                   <cal:supported-calendar-component-set/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/</d:href>
    <d:propstat>
      <d:prop>
        <cal:supported-calendar-component-set>
          <cal:comp name="VEVENT"/>
          <cal:comp name="VTODO"/>
        </cal:supported-calendar-component-set>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:cal="urn:ietf:params:xml:ns:caldav">
                 <d:prop>
                   <cal:supported-calendar-component-set/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:supported-calendar-component-set/cal:comp/@name' -n
VEVENT
VTODO

Get today's events.

$ curl --silent \
       --request REPORT \
        --header "Depth: 1" \
        --header "Content-Type: text/xml" \
        --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                  <d:prop><d:getetag /><c:calendar-data /></d:prop>
                  <c:filter>
                    <c:comp-filter name="VCALENDAR">
                      <c:comp-filter name="VEVENT">
                        <c:time-range  start="20171217T000000" end="20171218T000000"/>
                      </c:comp-filter>
                    </c:comp-filter>
                  </c:filter>
                </c:calendar-query>' \
        --user crono:zabie \
  https://cloud.example.org/remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/Nextcloud-KKZ132DLC63FFE1S4T9B.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"ba8c6d9009abb08fa4ea2be6cbe2ebe9"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171216T221307
DTSTAMP:20171216T221307
LAST-MODIFIED:20171217T012313
UID:JW3J5YXFDDADD3DUISWGNR
SUMMARY:Configure VPN for Netflix
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/Nextcloud-SLK1IPCBJPSIANH6LFY3TQ.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"0921b6eb221dbd4eadc68dc22ebc78dd"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171215T235237
DTSTAMP:20171215T235237
LAST-MODIFIED:20171217T012316
UID:VSTUTPGUYRHMWP6HXLP3G
SUMMARY:Renew car insurance
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                   <c:comp-filter name="VCALENDAR">
                     <c:comp-filter name="VEVENT">
                       <c:time-range  start="20171217T000000" end="20171218T000000"/>
                     </c:comp-filter>
                   </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171216T221307
DTSTAMP:20171216T221307
LAST-MODIFIED:20171217T012313
UID:JW3J5YXFDDADD3DUISWGNR
SUMMARY:Configure VPN for Netflix
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171215T235237
DTSTAMP:20171215T235237
LAST-MODIFIED:20171217T012316
UID:VSTUTPGUYRHMWP6HXLP3G
SUMMARY:Renew car insurance
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR

Get tasks to be done today.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                   <c:comp-filter name="VCALENDAR">
                     <c:comp-filter name="VTODO">
                       <c:prop-filter name="DUE">
                         <c:time-range start="20171217T000000" end="20171218T000000"/>
                         <c:is-defined/>
                       </c:prop-filter>
                       <c:prop-filter name="COMPLETED">
                         <c:is-not-defined/>
                       </c:prop-filter>
                     </c:comp-filter>
                   </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/Nextcloud-3kmehrxc5bmio2wj1g8geb.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"574bf3275954deb3912d3ee34cfcece5"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                   <c:comp-filter name="VCALENDAR">
                     <c:comp-filter name="VTODO">
                       <c:prop-filter name="DUE">
                         <c:time-range start="20171217T000000" end="20171218T000000"/>
                         <c:is-defined/>
                       </c:prop-filter>
                       <c:prop-filter name="COMPLETED">
                         <c:is-not-defined/>
                       </c:prop-filter>
                     </c:comp-filter>
                   </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR

Get overdue tasks.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                    <c:comp-filter name="VCALENDAR">
                      <c:comp-filter name="VTODO">
                        <c:prop-filter name="DUE">
                          <c:time-range end="20171217T000000"/>
                          <c:is-defined/>
                        </c:prop-filter>
                        <c:prop-filter name="COMPLETED">
                          <c:is-not-defined/>
                        </c:prop-filter>
                      </c:comp-filter>
                    </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/Nextcloud-dejyd26qk1vzx8lq938ct.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"c2d6081a16ce1982086c1073225e6595"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T235541
DTSTAMP:20171216T224646
LAST-MODIFIED:20171216T224646
UID:t9xf9ou7dz
SUMMARY:Inspect RFC4791
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171215T000000
STATUS:NEEDS-ACTION
CATEGORIES:Idea
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                    <c:comp-filter name="VCALENDAR">
                      <c:comp-filter name="VTODO">
                        <c:prop-filter name="DUE">
                          <c:time-range end="20171217T000000"/>
                          <c:is-defined/>
                        </c:prop-filter>
                        <c:prop-filter name="COMPLETED">
                          <c:is-not-defined/>
                        </c:prop-filter>
                      </c:comp-filter>
                    </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T235541
DTSTAMP:20171216T224646
LAST-MODIFIED:20171216T224646
UID:t9xf9ou7dz
SUMMARY:Inspect RFC4791
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171215T000000
STATUS:NEEDS-ACTION
CATEGORIES:Idea
END:VTODO
END:VCALENDAR

Get tasks that belong to "Idea" category but does not contain "RFC" string.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="SUMMARY">
                           <c:text-match negate-condition="yes" collation="i;ascii-casemap">rfc</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="CATEGORIES">
                           <c:text-match collation="i;ascii-casemap">idea</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">                                    
                           <c:is-not-defined/>                                               
                         </c:prop-filter> 
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>                              
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/Nextcloud-3kmehrxc5bmio2wj1g8geb.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"574bf3275954deb3912d3ee34cfcece5"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="SUMMARY">
                           <c:text-match negate-condition="yes" collation="i;ascii-casemap">rfc</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="CATEGORIES">
                           <c:text-match collation="i;ascii-casemap">idea</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">                                    
                           <c:is-not-defined/>                                               
                         </c:prop-filter> 
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>                              
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR

Get tasks without defined due date.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="DUE">
                           <c:is-not-defined/>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">
                           <c:is-not-defined/>
                         </c:prop-filter>
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/debian/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/debian/Nextcloud-4xuiemhm07tko9lfqumdhq.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"150984578c2b889bacb96df2ba8fdb8c"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T233039
DTSTAMP:20171216T215618
LAST-MODIFIED:20171216T215618
UID:8wddgk2ytvi
SUMMARY:Install OpenVPN
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/debian/Nextcloud-uxeoyrtcfhoo5orjm6gefi.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"3ff5ad3d4a982916d8995a59d77dd594"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T225943
DTSTAMP:20171216T215443
LAST-MODIFIED:20171216T215443
UID:ygs9uj2lh3n
SUMMARY:Upgrade iptables firewall
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="DUE">
                           <c:is-not-defined/>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">
                           <c:is-not-defined/>
                         </c:prop-filter>
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/debian/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T233039
DTSTAMP:20171216T215618
LAST-MODIFIED:20171216T215618
UID:8wddgk2ytvi
SUMMARY:Install OpenVPN
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T225943
DTSTAMP:20171216T215443
LAST-MODIFIED:20171216T215443
UID:ygs9uj2lh3n
SUMMARY:Upgrade iptables firewall
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR

Additional notes

You can return all property names and values on the calendar resource.

$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:allprop/>
               </d:propfind>' \
       --user crono:zabie \
  https://cloud.example.org//remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/</d:href>
    <d:propstat>
      <d:prop>
        <d:resourcetype>
          <d:collection/>
          <cal:calendar/>
        </d:resourcetype>
        <cs:getctag>http://sabre.io/ns/sync/57</cs:getctag>
        <s:sync-token>57</s:sync-token>
        <cal:supported-calendar-component-set>
          <cal:comp name="VEVENT"/>
          <cal:comp name="VTODO"/>
        </cal:supported-calendar-component-set>
        <cal:schedule-calendar-transp>
          <cal:opaque/>
        </cal:schedule-calendar-transp>
        <oc:owner-principal>principals/users/crono</oc:owner-principal>
        <d:displayname>Personal</d:displayname>
        <x1:calendar-order xmlns:x1="http://apple.com/ns/ical/">0</x1:calendar-order>
        <x1:calendar-color xmlns:x1="http://apple.com/ns/ical/">#63DA38</x1:calendar-color>
        <x2:owner-displayname xmlns:x2="http://nextcloud.com/ns">crono</x2:owner-displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>

References

How to create iptables firewall

$
0
0

Create iptables firewall that will be used to control incoming and outgoing traffic.

Create iptables firewall that will allow already established connections, incoming ssh on eth0 interface, outgoing icmp, ntp, dns, ssh, http and https.

# Flush INPUT/OUTPUT/FORWARD chains
iptables -F INPUT
iptables -F OUTPUT
iptables -F FORWARD

# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Accept everthing on loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept incoming packets for established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Accept incoming ICMP
iptables -A INPUT -p icmp -j ACCEPT

# Pass incoming SSH
iptables -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT

# Accept outgoing packets for established connections
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Accept outgoing DNS
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT

# Accept outgoing NTP
iptables -A OUTPUT -p tcp --dport 123 -j ACCEPT
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT

# Accept outgoing HTTP/S
iptables -A OUTPUT -p tcp --dport 80  -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

# Accept outgoing SSH
iptables -A OUTPUT -p tcp --dport 22  -j ACCEPT

# Accept outgoing ICMP
iptables -A OUTPUT -p icmp -j ACCEPT

# Drop everything else
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  DROP

List all firewall rules to verify that executed commands are applied as desired.

$ sudo iptables -L -v -n
Chain INPUT (policy DROP 268 packets, 14820 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
  620  102K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
  534  154K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53
    6   400 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:123
    2   152 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123
    2   120 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           

How to display upcoming events in Nextcloud calendar using shell script

$
0
0

Display upcoming events in Nextcloud calendar using shell script as described earlier.

Shell script using curl and xmlstarlet as described in the previous blog post.

#!/bin/bash
# Display upcoming events in Nextcloud calendar
# Blog posts:
#   https://blog.sleeplessbeastie.eu/2018/06/11/how-to-display-upcoming-events-in-nextcloud-calendar-using-text-based-terminal-emulator/
#   https://blog.sleeplessbeastie.eu/2018/06/18/how-to-display-upcoming-events-in-nextcloud-calendar-using-shell-script/

# CalDav server and path
dav_server="https://cloud.example.org"
dav_path="/remote.php/dav/"

# Basic auth credentials
username="crono"
password="zabie"

# Get URL for the user's principal resource on the server
dav_user_path=$(curl --silent \
                     --request PROPFIND \
                     --header 'Content-Type: text/xml' \
                     --header 'Depth: 0' \
                     --data '<d:propfind xmlns:d="DAV:">
                               <d:prop>
                                 <d:current-user-principal />
                               </d:prop>
                             </d:propfind>' \
                     --user ${username}:${password} \
                     ${dav_server}${dav_path} | \
                xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/d:current-user-principal/d:href' -n) 

# Get URL that contains calendar collections owned by the user
dav_user_calendar_home_path=$(curl --silent \
                              --request PROPFIND \
                              --header 'Content-Type: text/xml' \
                              --header 'Depth: 0' \
                              --data '<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                                        <d:prop>
                                          <c:calendar-home-set />
                                        </d:prop>
                                      </d:propfind>' \
                              --user ${username}:${password} \
                              ${dav_server}${dav_user_path} | \
                         xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-home-set/d:href' -n) 

# Get calendar paths                                      
dav_user_calendar_paths=$(curl --silent \
            	     --request PROPFIND \
                     --header 'Content-Type: text/xml' \
                     --header 'Depth: 1' \
                     --data '<d:propfind xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/"><d:prop><d:displayname/></d:prop></d:propfind>' \
                     --user ${username}:${password} \
                     ${dav_server}${dav_user_calendar_home_path} | \
		     xmlstarlet sel -t -m 'd:multistatus/d:response' -i  "string-length(d:propstat/d:prop/d:displayname)" -i "d:propstat/d:status='HTTP/1.1 200 OK'" -v "d:href" -n)

# Define start/end date
date_start=$(date  +"%Y%m%dT000000" -d today)
date_end=$(date  +"%Y%m%dT000000" -d tomorrow)

# Get data for each calendar
for dav_user_calendar_path in ${dav_user_calendar_paths}; do
  # calendar name
  calendar_name=$(curl --silent \
                       --request PROPFIND \
                       --header "Content-Type: text/xml" \
                       --header 'Depth: 0' \
                       --user ${username}:${password} \
                       ${dav_server}${dav_user_calendar_path} | \
                       xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/d:displayname' -n)
  # event types stored in this calendar e.g. VTODO, VEVENTS
  component_types=$(curl --silent \
                         --request PROPFIND \
                         --header "Content-Type: text/xml" \
                         --header 'Depth: 0' \
                         --user ${username}:${password} \
                         ${dav_server}${dav_user_calendar_path} | \
                    xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:supported-calendar-component-set/cal:comp/@name' -n)

  # initial values
  events=""
  events_count="0"
  todos=""
  todos_count="0"
  overdue_todos=""
  overdue_todos_count="0"
  general_todos=""
  general_todos_count="0"
  inotrfc_todos=""
  inotrfc_todos_count="0"

  case "${component_types}" in
    *VEVENT*)
      # Today's events
      events=$(curl --silent \
                    --request REPORT \
                    --header "Depth: 1" \
                    --header "Content-Type: text/xml" \
                    --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                              <d:prop><d:getetag /><c:calendar-data /></d:prop>
                              <c:filter>
                                <c:comp-filter name="VCALENDAR">
                                  <c:comp-filter name="VEVENT">
                                     <c:time-range  start="'${date_start}'" end="'${date_end}'"/>
                                  </c:comp-filter>
                                </c:comp-filter>
                              </c:filter>
                            </c:calendar-query>' \
                    --basic \
                    --user ${username}:${password} \
                    ${dav_server}${dav_user_calendar_path} | \
		    xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n | \
               grep ^SUMMARY)
      events_count=$(echo "$events" | grep -c ^SUMMARY)
      ;&
    *VTODO*)
      # Tasks to be done today
      todos=$(curl --silent \
                    --request REPORT \
                    --header "Depth: 1" \
                    --header "Content-Type: text/xml" \
                    --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                              <d:prop><d:getetag /><c:calendar-data /></d:prop>
                              <c:filter>
                                <c:comp-filter name="VCALENDAR">
                                  <c:comp-filter name="VTODO">
                                     <c:prop-filter name="DUE">
                                       <c:time-range start="'${date_start}'" end="'${date_end}'"/>
                                       <c:is-defined/>
                                     </c:prop-filter>
                                     <c:prop-filter name="COMPLETED">
                                       <c:is-not-defined/>
                                     </c:prop-filter>
                                  </c:comp-filter>
                                </c:comp-filter>
                              </c:filter>
                            </c:calendar-query>' \
                    --user ${username}:${password} \
                    ${dav_server}${dav_user_calendar_path} | \
               xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n | \
                            
               grep ^SUMMARY)
      todos_count=$(echo "$todos" | grep -c ^SUMMARY)

      # Overdue tasks
      overdue_todos=$(curl --silent \
                    --request REPORT \
                    --header "Depth: 1" \
                    --header "Content-Type: text/xml" \
                    --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                              <d:prop><d:getetag /><c:calendar-data /></d:prop>
                              <c:filter>
                                <c:comp-filter name="VCALENDAR">
                                  <c:comp-filter name="VTODO">
                                     <c:prop-filter name="DUE">
                                     <c:time-range end="'${date_start}'"/>
                                       <c:is-defined/>
                                     </c:prop-filter>
                                     <c:prop-filter name="COMPLETED">
                                       <c:is-not-defined/>
                                     </c:prop-filter>
                                  </c:comp-filter>
                                </c:comp-filter>
                              </c:filter>
                            </c:calendar-query>' \
                    --user ${username}:${password} \
                    ${dav_server}${dav_user_calendar_path} | \
               xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n | \
               grep ^SUMMARY)
      overdue_todos_count=$(echo "$overdue_todos" | grep -c ^SUMMARY)

      # Tasks that belong to "Idea" category but does not contain RFC string
      inotrfc_todos=$(curl --silent \
                    --request REPORT \
                    --header "Depth: 1" \
                    --header "Content-Type: text/xml" \
                    --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                              <d:prop><d:getetag /><c:calendar-data /></d:prop>
                              <c:filter>
                                <c:comp-filter name="VCALENDAR">
                                  <c:comp-filter name="VTODO">
                                    <c:prop-filter name="SUMMARY">
                                      <c:text-match negate-condition="yes" collation="i;ascii-casemap">rfc</c:text-match>
                                    </c:prop-filter>
                                    <c:prop-filter name="CATEGORIES">
                                      <c:text-match collation="i;ascii-casemap">idea</c:text-match>
                                    </c:prop-filter>
                                    <c:prop-filter name="COMPLETED">                                    
                                      <c:is-not-defined/>                                               
                                    </c:prop-filter> 
                                  </c:comp-filter>
                                </c:comp-filter>
                              </c:filter>                              
                            </c:calendar-query>' \
                    --user ${username}:${password} \
                    ${dav_server}${dav_user_calendar_path} | \
               xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n | \
               grep ^SUMMARY)
      inotrfc_todos_count=$(echo "$inotrfc_todos" | grep -c ^SUMMARY)

      # Tasks without defined due date
      general_todos=$(curl --silent \
                    --request REPORT \
                    --header "Depth: 1" \
                    --header "Content-Type: text/xml" \
                    --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                              <d:prop><d:getetag /><c:calendar-data /></d:prop>
                              <c:filter>
                                <c:comp-filter name="VCALENDAR">
                                  <c:comp-filter name="VTODO">
                                     <c:prop-filter name="DUE">
                                       <c:is-not-defined/>
                                     </c:prop-filter>
                                     <c:prop-filter name="COMPLETED">
                                       <c:is-not-defined/>
                                     </c:prop-filter>
                                  </c:comp-filter>
                                </c:comp-filter>
                              </c:filter>
                            </c:calendar-query>' \
                    --user ${username}:${password} \
                    ${dav_server}${dav_user_calendar_path} | \
               xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n | \
               grep ^SUMMARY)
      general_todos_count=$(echo "$general_todos" | grep -c ^SUMMARY)
      ;&
  esac

  if [ "$events_count"        -ge "1" ] || \
     [ "$todos_count"         -ge "1" ] || \
     [ "$overdue_todos_count" -ge "1" ] || \
     [ "$general_todos_count" -ge "1" ] || \
     [ "$inotrfc_todos_count" -ge "1" ]; then
    echo $calendar_name 
  
    if [ "$events_count" -ge "1" ]; then
      echo "  Today's events: "
      printf "$events\n" | tr -s "\n" | sed "s/^SUMMARY://" | while read event; do
        echo "    * $event"
      done  
    else
      echo "  There are no events for today"
    fi

    if [ "$todos_count" -ge "1" ]; then
      echo "  Tasks to be done today"
      printf "$todos\n" | tr -s "\n" | sed "s/^SUMMARY://" | while read todo; do
        echo "    * $todo"
      done  
    else
      echo "  There are no tasks to be done today"
    fi

    if [ "$overdue_todos_count" -ge "1" ]; then
      echo "  Overdue tasks"
      printf "$overdue_todos\n" | tr -s "\n" | sed "s/^SUMMARY://" | while read todo; do
        echo "    * $todo"
      done  
    fi

    if [ "$inotrfc_todos_count" -ge "1" ]; then
      echo "  Tasks that belong to the \"Idea\" category but does not contain RFC string"
      printf "$inotrfc_todos\n" | tr -s "\n" | sed "s/^SUMMARY://" | while read todo; do
        echo "    * $todo"
      done  
    else
      echo "  There are no tasks that belong to the \"Idea\" category but does not contain RFC string"
    fi

    if [ "$general_todos_count" -ge "1" ]; then
      echo "  Tasks without defined due date"
      printf "$general_todos\n" | tr -s "\n" | sed "s/^SUMMARY://" | while read todo; do
        echo "    * $todo"
      done  
    else
      echo "  There are no tasks without defined due date"
    fi
  fi
done

Shell script output.

Personal
  Today's events: 
    * Configure VPN for Netflix
    * Renew car insurance
  There are no tasks to be done today
  There are no tasks that belong to the "Idea" category but does not contain RFC string
  Tasks without defined due date
    * Buy Radiant Historia for DS
Debian
  There are no events for today
  There are no tasks to be done today
  There are no tasks that belong to the "Idea" category but does not contain RFC string
  Tasks without defined due date
    * Install OpenVPN
    * Upgrade iptables firewall
Work
  There are no events for today
  There are no tasks to be done today
  There are no tasks that belong to the "Idea" category but does not contain RFC string
  Tasks without defined due date
    * Reset password for Safari Books Online
Projects
  Today's events: 
    * Install and configure Dokuwiki
  Tasks to be done today
    * Create blog post about CalDAV
  Overdue tasks
    * Inspect RFC4791
  Tasks that belong to the "Idea" category but does not contain RFC string
    * Create blog post about CalDAV
  There are no tasks without defined due date

This shell script and the prevoius blog post should be enough to create useful solution that filter calendar events and display these or send further using API to Slack or other service.

How to create iptables firewall using custom chains

$
0
0

Create iptables firewall using custom chains that will be used to control incoming and outgoing traffic.

Create iptables firewall that will allow already established connections, incoming ssh for given source addresses, outgoing icmp, ntp, dns, ssh, http and https.

# Flush rules and delete custom chains
iptables -F
iptables -X

# Define chain to allow particular source addresses
iptables -N chain-incoming-ssh
iptables -A chain-incoming-ssh -s 192.168.1.148 -j ACCEPT
iptables -A chain-incoming-ssh -s 192.168.1.149 -j ACCEPT
iptables -A chain-incoming-ssh -j DROP

# Define chain to allow particular services
iptables -N chain-outgoing-services
iptables -A chain-outgoing-services -p tcp --dport 53  -j ACCEPT
iptables -A chain-outgoing-services -p udp --dport 53  -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 123 -j ACCEPT
iptables -A chain-outgoing-services -p udp --dport 123 -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 80  -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 443 -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 22  -j ACCEPT
iptables -A chain-outgoing-services -p icmp            -j ACCEPT
iptables -A chain-outgoing-services -j DROP

# Define chain to allow established connections
iptables -N chain-states
iptables -A chain-states -p tcp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -p udp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -p icmp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -j RETURN

# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Accept everthing on loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept incoming/outgoing packets for established connections
iptables -A INPUT  -j chain-states
iptables -A OUTPUT -j chain-states

# Accept incoming ICMP
iptables -A INPUT -p icmp -j ACCEPT

# Accept incoming SSH
iptables -A INPUT -p tcp --dport 22 -j chain-incoming-ssh

# Accept outgoing 
iptables -A OUTPUT -j chain-outgoing-services

## Drop everything else
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  DROP

List all firewall rules to verify that executed commands are applied as desired.

$ sudo iptables -L -v -n
Chain INPUT (policy DROP 2 packets, 92 bytes)
 pkts bytes target              prot opt in     out     source               destination         
    0     0 DROP                all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    1    29 ACCEPT              all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
  190 15639 chain-states        all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT              icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    60 chain-incoming-ssh  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target        prot opt in     out     source               destination         
    1    29 ACCEPT        all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
  150 39893 chain-states  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    62 chain-outgoing-services  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-ssh (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       192.168.1.148        0.0.0.0/0           
    1    60 ACCEPT     all  --  *      *       192.168.1.149        0.0.0.0/0           
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-outgoing-services (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53
    1    62 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:123
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-states (2 references)
 pkts bytes target     prot opt in     out     source               destination         
  335 55240 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    1    78 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    4   214 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

I have used RETURN target for chain-states so the packet would continue its passage through the firewall.

Editing address list

List firewall rules in source address list.

$ sudo iptables -n --list chain-incoming-ssh --line-numbers
Chain chain-incoming-ssh (1 references)
num  target     prot opt source               destination         
1    ACCEPT     all  --  192.168.1.148        0.0.0.0/0           
2    ACCEPT     all  --  192.168.1.149        0.0.0.0/0           
3    DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Remove first entry.

sudo iptables -D chain-incoming-ssh  1

Add new rule at the beginning of the chain.

$ sudo iptables -I chain-incoming-ssh 1 -s 192.168.1.140 -j ACCEPT 

Add new rule just one before last entry.

$ sudo iptables -I chain-incoming-ssh 3 -s 192.168.1.150 -j ACCEPT 

List firewall rules in source address list.

$ sudo iptables -n --list chain-incoming-ssh --line-numbers
Chain chain-incoming-ssh (1 references)
num  target     prot opt source               destination         
1    ACCEPT     all  --  192.168.1.140        0.0.0.0/0           
2    ACCEPT     all  --  192.168.1.149        0.0.0.0/0           
3    ACCEPT     all  --  192.168.1.150        0.0.0.0/0           
4    DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Additional notes

You can inspect particular chain using the following command.

$ sudo iptables -n --list chain-outgoing-services
Chain chain-outgoing-services (1 references)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:123
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:123
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:443
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
DROP       all  --  0.0.0.0/0            0.0.0.0/0    

You can also use it inside shell script to peform different actions depending on its existence.

iptables --list chain-outgoing-services &>/dev/null
if [ "$?" -eq "0" ]; then
  iptables -F chain-outgoing-services
else
  iptables -N chain-outgoing-services
fi

How to disable USB device

$
0
0

Whitelist or render inoperative any USB device to secure your personal belongings.

Disable particular USB device

I will disable cheap Samsung tablet, so it won't mount when connected.

Display USB devices before the device is connected.

$ lsusb
Bus 001 Device 002: ID 8087:8001 Intel Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 093: ID 0bda:5682 Realtek Semiconductor Corp. 
Bus 002 Device 003: ID 8087:0a2a Intel Corp. 
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Display USB devices after device is connected.

$ lsusb
Bus 001 Device 002: ID 8087:8001 Intel Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 093: ID 0bda:5682 Realtek Semiconductor Corp. 
Bus 002 Device 003: ID 8087:0a2a Intel Corp. 
Bus 002 Device 029: ID 04e8:6860 Samsung Electronics Co., Ltd Galaxy (MTP)
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Mentioned device can be identified by vendor id 0x04e8Samsung Electronics Co., Ltd and product id 0x6860Galaxy (MTP).

Identify device path to display more useful udev information.

$ idVendor="04e8"; idProduct="6860"; \
  find -L /sys/bus/usb/devices/ -maxdepth 2 -name idVendor -exec grep -l $idVendor {} \; | \
    while read line; do \
      location=$(dirname $line); \
      grep -q $idProduct $location/idProduct; 
      if [ "$?" -eq "0" ]; then \
        echo $location; 
      fi 
    done
/sys/bus/usb/devices/2-2

Use device path to display udev information.

$ udevadm info -a -p /sys/bus/usb/devices/2-2

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:14.0/usb2/2-2':
    KERNEL=="2-2"
    SUBSYSTEM=="usb"
    DRIVER=="usb"
    ATTR{authorized}=="0"
    ATTR{avoid_reset_quirk}=="0"
    ATTR{bConfigurationValue}==""
    ATTR{bDeviceClass}=="00"
    ATTR{bDeviceProtocol}=="01"
    ATTR{bDeviceSubClass}=="00"
    ATTR{bMaxPacketSize0}=="64"
    ATTR{bMaxPower}==""
    ATTR{bNumConfigurations}=="1"
    ATTR{bNumInterfaces}==""
    ATTR{bcdDevice}=="0400"
    ATTR{bmAttributes}==""
    ATTR{busnum}=="2"
    ATTR{configuration}==""
    ATTR{devnum}=="29"
    ATTR{devpath}=="2"
    ATTR{idProduct}=="6860"
    ATTR{idVendor}=="04e8"
    ATTR{ltm_capable}=="no"
    ATTR{manufacturer}=="SAMSUNG"
    ATTR{maxchild}=="0"
    ATTR{product}=="SAMSUNG_Android"
    ATTR{quirks}=="0x0"
    ATTR{removable}=="removable"
    ATTR{serial}=="3801a4eaab8a3400"
    ATTR{speed}=="480"
    ATTR{urbnum}=="32"
    ATTR{version}==" 2.00"

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2':
    KERNELS=="usb2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{authorized_default}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bMaxPower}=="0mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bcdDevice}=="0411"
    ATTRS{bmAttributes}=="e0"
    ATTRS{busnum}=="2"
    ATTRS{configuration}==""
    ATTRS{devnum}=="1"
    ATTRS{devpath}=="0"
    ATTRS{idProduct}=="0002"
    ATTRS{idVendor}=="1d6b"
    ATTRS{interface_authorized_default}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Linux 4.11.0-13-generic xhci-hcd"
    ATTRS{maxchild}=="11"
    ATTRS{product}=="xHCI Host Controller"
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="unknown"
    ATTRS{serial}=="0000:00:14.0"
    ATTRS{speed}=="480"
    ATTRS{urbnum}=="8014"
    ATTRS{version}==" 2.00"

  looking at parent device '/devices/pci0000:00/0000:00:14.0':
    KERNELS=="0000:00:14.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="xhci_hcd"
    ATTRS{broken_parity_status}=="0"
    ATTRS{class}=="0x0c0330"
    ATTRS{consistent_dma_mask_bits}=="64"
    ATTRS{d3cold_allowed}=="1"
    ATTRS{device}=="0x9cb1"
    ATTRS{dma_mask_bits}=="64"
    ATTRS{driver_override}=="(null)"
    ATTRS{enable}=="1"
    ATTRS{irq}=="44"
    ATTRS{local_cpulist}=="0-3"
    ATTRS{local_cpus}=="f"
    ATTRS{msi_bus}=="1"
    ATTRS{numa_node}=="-1"
    ATTRS{revision}=="0x03"
    ATTRS{subsystem_device}=="0x0665"
    ATTRS{subsystem_vendor}=="0x1028"
    ATTRS{vendor}=="0x8086"

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

Create udev rule to deauthorize this device early on.

$ cat << EOF | sudo tee /etc/udev/rules.d/01-disable-samsung.rules
SUBSYSTEM=="usb", ACTION=="add", ATTR{removable}=="removable", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6860", ATTR{authorized}="0"
EOF

This device will be unusable next time it is connected to the computer.

Whitelist USB devices

Display basic USB devices.

$ lsusb
lsusb 
Bus 001 Device 002: ID 8087:8001 Intel Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 093: ID 0bda:5682 Realtek Semiconductor Corp. 
Bus 002 Device 003: ID 8087:0a2a Intel Corp. 
Bus 002 Device 012: ID 045e:0745 Microsoft Corp. Nano Transceiver v1.0 for Bluetooth
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Authorize these USB devices and their childrens, but only if device as a parent is not a hub.

$ cat << EOF | sudo tee /etc/udev/rules.d/01-usb-whitelist.rules
ACTION    != "add", GOTO = "usblist_rules_end"
SUBSYSTEM == "usb", GOTO = "usblist_usb_rules"
GOTO="usblist_rules_end"

LABEL="usblist_usb_rules"

# Bus 001 Device 002: ID 8087:8001 Intel Corp. 
ATTR{idVendor}  == "8087", ATTR{idProduct}  == "8001",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "8087", ATTRS{idProduct} == "8001", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
ATTR{idVendor}  == "1d6b", ATTR{idProduct}  == "0002",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "1d6b", ATTRS{idProduct} == "0002", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
ATTR{idVendor}  == "1d6b", ATTR{idProduct}  == "0003",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "1d6b", ATTRS{idProduct} == "0003", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Bus 002 Device 093: ID 0bda:5682 Realtek Semiconductor Corp. 
ATTR{idVendor}  == "0bda", ATTR{idProduct}  == "5682",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "0bda", ATTRS{idProduct} == "5682", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Bus 002 Device 003: ID 8087:0a2a Intel Corp. 
ATTR{idVendor}  == "8087", ATTR{idProduct}  == "0a2a",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "8087", ATTRS{idProduct} == "0a2a", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Bus 002 Device 012: ID 045e:0745 Microsoft Corp. Nano Transceiver v1.0 for Bluetooth
ATTR{idVendor}  == "045e", ATTR{idProduct}  == "0745",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "045e", ATTRS{idProduct} == "0745", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
ATTR{idVendor}  == "1d6b", ATTR{idProduct}  == "0002",                              GOTO = "usblist_rules_end"
ATTRS{idVendor} == "1d6b", ATTRS{idProduct} == "0002", ATTRS{bDeviceClass} != "09", GOTO = "usblist_rules_end"

# Other USB devices - deauthorize
ATTR{authorized}="0"

LABEL="usblist_rules_end"
EOF

References

Authorizing (or not) your USB devices to connect to the system

USB Class Codes


How to enable HTTPS protocol for package managers using libapt-pkg library

$
0
0

Enable HTTPS (Hypertext Transfer Protocol Secure) for package managers using libapt-pkg library.

You can easily identify that HTTPS transport is missing during regular package management operations as error messages are very clear.

$ sudo apt-get update
Get:1 http://security.debian.org stretch/updates InRelease [63.0 kB]
Ign:2 http://deb.debian.org/debian stretch InRelease
Hit:3 http://deb.debian.org/debian stretch Release
Get:4 http://security.debian.org stretch/updates/main amd64 Packages [269 kB]
Get:5 http://security.debian.org stretch/updates/main Translation-en [118 kB]
Fetched 450 kB in 0s (961 kB/s)
Reading package lists... Done
E: The method driver /usr/lib/apt/methods/https could not be found.
N: Is the package apt-transport-https installed?
E: Failed to fetch https://dl.bintray.com/rabbitmq/debian/dists/stretch/InRelease
E: Some index files failed to download. They have been ignored, or old ones used instead.

Install missing apt-transport-https package.

$ sudo apt-get install apt-transport-https
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  libcurl3-gnutls
The following NEW packages will be installed:
  apt-transport-https libcurl3-gnutls
0 upgraded, 2 newly installed, 0 to remove and 6 not upgraded.
Need to get 460 kB of archives.
After this operation, 873 kB of additional disk space will be used.
Do you want to continue? [Y/n]
Get:1 http://deb.debian.org/debian stretch/main amd64 libcurl3-gnutls amd64 7.52.1-5+deb9u3 [289 kB]
Get:2 http://deb.debian.org/debian stretch/main amd64 apt-transport-https amd64 1.4.8 [171 kB]
Fetched 460 kB in 0s (9505 kB/s)
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package libcurl3-gnutls:amd64.
(Reading database ... 17075 files and directories currently installed.)
Preparing to unpack .../libcurl3-gnutls_7.52.1-5+deb9u3_amd64.deb ...
Unpacking libcurl3-gnutls:amd64 (7.52.1-5+deb9u3) ...
Selecting previously unselected package apt-transport-https.
Preparing to unpack .../apt-transport-https_1.4.8_amd64.deb ...
Unpacking apt-transport-https (1.4.8) ...
Setting up libcurl3-gnutls:amd64 (7.52.1-5+deb9u3) ...
Processing triggers for libc-bin (2.24-11+deb9u1) ...
Setting up apt-transport-https (1.4.8) ...

Now you can use HTTPS protocol without any problems.

$ sudo apt-get update
Hit:1 http://security.debian.org stretch/updates InRelease
Ign:2 http://deb.debian.org/debian stretch InRelease
Hit:3 http://deb.debian.org/debian stretch Release
Ign:4 https://dl.bintray.com/rabbitmq/debian stretch InRelease
Hit:6 https://dl.bintray.com/rabbitmq/debian stretch Release
Reading package lists... Done

How to display package dependencies

$
0
0

Display dependencies to inspect which packages are required for given application.

Use apt-cache utility to display package dependencies

Display dependencies for particular package located in configured repository.

$ apt-cache depends tmux
tmux
  Depends: libc6
  Depends: libevent-2.0-5
  Depends: libtinfo5
  Depends: libutempter0

Recursively display dependencies for particualar package located in configured repository.

$ apt-cache depends --recurse tmux
tmux
  Depends: libc6
  Depends: libevent-2.0-5
  Depends: libtinfo5
  Depends: libutempter0
libc6
  Depends: libgcc1
  Breaks: <hurd>
  Breaks: libtirpc1
  Breaks: locales
  Breaks: locales-all
  Breaks: <lsb-core>
  Breaks: nscd
  Suggests: glibc-doc
 |Suggests: debconf
  Suggests: <debconf-2.0>
    cdebconf
    debconf
  Suggests: libc-l10n
  Suggests: locales
  Replaces: <libc6-amd64>
libevent-2.0-5
  Depends: libc6
libtinfo5
  Depends: libc6
[...]

There is too much information in the above example, but it can be easily limited.

$ apt-cache depends --no-recommends --no-suggests --no-replaces --no-conflicts --no-breaks tmux | awk '{print $NF}' | xargs apt-cache depends
tmux
  Depends: libc6
  Depends: libevent-2.0-5
  Depends: libtinfo5
  Depends: libutempter0
libc6
  Depends: libgcc1
  Breaks: <hurd>
  Breaks: libtirpc1
  Breaks: locales
  Breaks: locales-all
  Breaks: <lsb-core>
  Breaks: nscd
  Suggests: glibc-doc
 |Suggests: debconf
  Suggests: <debconf-2.0>
    cdebconf
    debconf
  Suggests: libc-l10n
  Suggests: locales
  Replaces: <libc6-amd64>
libevent-2.0-5
  Depends: libc6
libtinfo5
  Depends: libc6
  Breaks: dialog
  Replaces: libncurses5
libutempter0
  Depends: libc6

Use aptitude utility to display package dependencies

Install a high-level interface to the package manager.

$ sudo apt-get install aptitude

Display dependencies for particular package located in configured repository.

$ aptitude show tmux | grep ^Depends
Depends: libc6 (>= 2.14), libevent-2.0-5 (>= 2.0.10-stable), libtinfo5 (>= 6), libutempter0 (>= 1.1.5)

Use apt-rdepends utility to display package dependencies

Install an utility that performs recursive dependency listings similar to apt-cache.

$ sudo apt-get install apt-rdepends

Display dependencies for particular package located in configured repository.

$ apt-rdepends --print-state tmux --state-follow=none
tmux
  Depends: libc6 (>= 2.14) [Installed]
  Depends: libevent-2.0-5 (>= 2.0.10-stable) [Installed]
  Depends: libtinfo5 (>= 6) [Installed]
  Depends: libutempter0 (>= 1.1.5) [Installed]

Recursively display dependencies for particualar package located in configured repository.

$ apt-rdepends --print-state tmux
Reading package lists... Done
Building dependency tree       
Reading state information... Done
tmux
  Depends: libc6 (>= 2.14) [Installed]
  Depends: libevent-2.0-5 (>= 2.0.10-stable) [Installed]
  Depends: libtinfo5 (>= 6) [Installed]
  Depends: libutempter0 (>= 1.1.5) [Installed]
libc6
  Depends: libgcc1 [Installed]
libgcc1
  Depends: gcc-6-base (= 6.3.0-18) [Installed]
  Depends: libc6 (>= 2.14) [Installed]
gcc-6-base
libevent-2.0-5
  Depends: libc6 (>= 2.17) [Installed]
libtinfo5
  Depends: libc6 (>= 2.16) [Installed]
libutempter0
  Depends: libc6 (>= 2.4) [Installed]

Use dpkg utility to display package dependencies

Display dependencies for particular Debian binary package.

$ dpkg --info /var/cache/apt/archives/tmux_2.3-4_amd64.deb | grep ^\ Depends:
 Depends: libc6 (>= 2.14), libevent-2.0-5 (>= 2.0.10-stable), libtinfo5 (>= 6), libutempter0 (>= 1.1.5)

How to log dropped connections from iptables firewall using rsyslog

$
0
0

Log dropped connections from iptables firewall using rsyslog for further analysis and troubleshooting.

Configure rsyslog to use /var/log/firewall.log log file for firewall events.

$ cat << EOF | sudo tee /etc/rsyslog.d/firewall.conf
# Log messages generated by iptables firewall to file
:msg,contains,"[fw-" /var/log/firewall.log
# stop processing it further
& stop
EOF

Apply rsyslog configuration.

$ sudo systemctl restart rsyslog

Rotate log file to prevent to conserve disk space.

$ cat << EOF | sudo tee /etc/logrotate.d/firewall.conf
/var/log/firewall.log
{
  rotate 7
  daily
  missingok
  notifempty
  delaycompress
  compress
  postrotate
  invoke-rc.d rsyslog rotate > /dev/null
  endscript
}
EOF

Create iptables firewall that will allow already established connections, incoming icmp and ssh, outgoing icmp, ntp, dns, ssh, http and https. It will also log invalid packets and those dropped ones.

# Flush rules and delete custom chains
iptables -F
iptables -X

# Define chain to allow particular source addresses
iptables -N chain-incoming-ssh 
iptables -A chain-incoming-ssh -s 192.168.1.149 -j ACCEPT -m comment --comment "local access"
iptables -A chain-incoming-ssh -p tcp --dport 22 -j LOG  --log-prefix "[fw-inc-ssh] " -m limit --limit 6/min --limit-burst 4
iptables -A chain-incoming-ssh -j DROP

# Define chain to log and drop incoming packets
iptables -N chain-incoming-log-and-drop
iptables -A chain-incoming-log-and-drop -j LOG --log-prefix "[fw-inc-drop] " -m limit --limit 6/min --limit-burst 4
iptables -A chain-incoming-log-and-drop -j DROP

# Define chain to log and drop outgoing packets
iptables -N chain-outgoing-log-and-drop
iptables -A chain-outgoing-log-and-drop -j LOG --log-prefix "[fw-out-drop] " -m limit --limit 6/min --limit-burst 4 
iptables -A chain-outgoing-log-and-drop -j DROP

# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j chain-incoming-log-and-drop

# Accept everthing on loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# ACCEPT incoming packets for established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Accept incoming ICMP
iptables -A INPUT -p icmp -j ACCEPT

# Accept incoming SSH
iptables -A INPUT -p tcp --dport 22 -j chain-incoming-ssh

# Accept outgoing packets for established connections
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Accept outgoing DNS
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT

# Accept outgoing NTP
iptables -A OUTPUT -p tcp --dport 123 -j ACCEPT
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT

# Accept outgoing HTTP/S
iptables -A OUTPUT -p tcp --dport 80  -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

# Accept outgoing SSH
iptables -A OUTPUT -p tcp --dport 22  -j ACCEPT

# Accept outgoing ICMP
iptables -A OUTPUT -p icmp -j ACCEPT

# Log not accounted outgoing traffic
iptables -A OUTPUT -j chain-outgoing-log-and-drop

# Log not accounted forwarding traffic
iptables -A FORWARD -j chain-incoming-log-and-drop

# Drop everything else
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  DROP

List all firewall rules to verify that executed commands are applied as desired.

$ sudo iptables -L -v -n
Chain INPUT (policy DROP 14 packets, 756 bytes)
 pkts bytes target                       prot opt in     out     source               destination         
    0     0 chain-incoming-log-and-drop  all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    0     0 ACCEPT                       all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
  515  188K ACCEPT                       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT                       icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    60 chain-incoming-ssh           tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target                       prot opt in     out     source               destination         
    0     0 chain-incoming-log-and-drop  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target                       prot opt in     out     source               destination         
    0     0 ACCEPT                       all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
  380 70413 ACCEPT                       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT                       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53
    8   530 ACCEPT                       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53
    0     0 ACCEPT                       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:123
    0     0 ACCEPT                       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123
    2   120 ACCEPT                       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT                       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
    0     0 ACCEPT                       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT                       icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    2   120 chain-outgoing-log-and-drop  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-log-and-drop (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            limit: avg 6/min burst 4 LOG flags 0 level 4 prefix "[fw-inc-drop] "
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-ssh (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       192.168.1.149        0.0.0.0/0            /* local access */
    1    60 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 limit: avg 6/min burst 4 LOG flags 0 level 4 prefix "[fw-inc-ssh] "
    1    60 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-outgoing-log-and-drop (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    2   120 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            limit: avg 6/min burst 4 LOG flags 0 level 4 prefix "[fw-out-drop] "
    2   120 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Sample log entries.

$ sudo tail /var/log/firewall.log 
Dec 31 13:48:24 debian kernel: [29030.017110] [fw-out-drop] IN= OUT=eth0 SRC=192.168.1.131 DST=84.16.240.28 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=63807 DF PROTO=TCP SPT=34610 DPT=8080 WINDOW=29200 RES=0x00 SYN URGP=0 
Dec 31 13:48:25 debian kernel: [29031.040102] [fw-out-drop] IN= OUT=eth0 SRC=192.168.1.131 DST=84.16.240.28 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=63808 DF PROTO=TCP SPT=34610 DPT=8080 WINDOW=29200 RES=0x00 SYN URGP=0 
Dec 31 13:48:29 debian kernel: [29034.614317] [fw-inc-ssh] IN=eth0 OUT= MAC=52:54:00:26:97:1f:b8:27:eb:0e:4e:b6:08:00 SRC=192.168.1.252 DST=192.168.1.131 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=25292 DF PROTO=TCP SPT=55354 DPT=22 WINDOW=29200 RES=0x00 SYN URGP=0 

How to locate directories that contain one or multiple files with particular content

$
0
0

Use find and grep commands to search for directories that contain one or multiple files with particular content.

I have described three different cases that can be used to locate particular directories: based on single file, multiple files or extended single file operations.

Search for mounted devices using specific option

I will inspect mounted filesystem options to display to display those mounted in read-write mode.

Locate options file that contain rw line, print directory name.

$ find /proc/fs/ -type f -name options -exec grep -l ^rw$ {} \; | xargs dirname
/proc/fs/ext4/dm-1
/proc/fs/ext4/sda2

Strip leading directory components to display only directory name.

$ find /proc/fs/ -type f -name options -exec grep -l ^rw$ {} \; | xargs dirname | xargs basename -a

or

$ find /proc/fs/ -type f -name options -exec grep -l ^rw$ {} \; | xargs dirname | awk --field-separator '/''{print $NF}'
dm-1
sda2

Search for path describing particular USB device

I will inspect idVendor and idProduct files to find path for Realtek0bda:5682 device.

$ find -L /sys/bus/usb/devices/ -maxdepth 2 -type f -name idVendor -exec grep -l "0bda" {} \; | \
    while read line; do \
      location=$(dirname $line); \
      grep -q "5682" $location/idProduct;
      if [ "$?" -eq "0" ]; then \
        echo $location;
      fi
    done
/sys/bus/usb/devices/2-5

Search for CPUs running at high speed

I will inspect current CPUs frequency and display those running at high speed (> 2 GHz).

$ find /sys/devices/system/cpu -maxdepth 3 -type f -name cpuinfo_cur_freq | \
    while read file; do \
      content=$(sudo cat $file); \
      freq=$(expr $content / 1000); \
      if [ "$freq" -gt "2000" ]; then \
        echo $file | \
          xargs dirname | xargs basename -a | \
          sed s/policy/cpu/; \
      fi \
    done
cpu3
cpu1

How to execute logrotate every hour

$
0
0

Logrotate does not support hourly schedule, but this is an easy task to accomplish.

Create separate directory to store hourlylogrotate configuration files.

$ sudo mkdir /etc/logrotate.hourly.d

Create main logrotate configuration file that will read configuration files from designated directory.

$ cat << EOF | sudo tee /etc/logrotate.hourly.conf
# packages drop hourly log rotation information into this directory
include /etc/logrotate.hourly.d
EOF

Set proper persmissions.

$ sudo chmod 644 /etc/logrotate.hourly.conf

Create cron configuration to execute logrotate every hour and read main hourly configuration file.

$ cat << EOF | sudo tee /etc/cron.hourly/logrotate
#!/bin/bash
test -x /usr/sbin/logrotate || exit 0
/usr/sbin/logrotate /etc/logrotate.hourly.conf
EOF

Set proper persmissions.

$ sudo chmod 775 /etc/cron.hourly/logrotate

Create configuration file to hourly rotate specific log file inside above-mentioned directory.

$ cat << EOF | sudo tee /etc/logrotate.hourly.d/application
/var/log/application.log {
  size 128M
  rotate 1
  copytruncate
  missingok
  notifempty
  nocreate
  nomail
}

Set proper persmissions.

$ sudo chmod 644 /etc/logrotate.hourly.d/application

Do not specify weekly, monthly or yearly parameter.

Viewing all 770 articles
Browse latest View live