-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
config.sh
executable file
·677 lines (597 loc) · 20 KB
/
config.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
#!/bin/sh
# ------------------------------------------------------------------------------
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org>
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Configuration file for TLS session ticket rotation program.
#
# AUTHOR: Richard Fussenegger <[email protected]>
# COPYRIGHT: Copyright (c) 2013 Richard Fussenegger
# LICENSE: http://unlicense.org/ PD
# LINK: http://richard.fussenegger.info/
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# User Configurable Variables
# ------------------------------------------------------------------------------
# The session ticket keys rotation interval as cron mask.
#
# Default is 12 hours which means that a key will reside in memory for 36 hours
# before it's deleted (three keys are used). You shouldn't go for much more
# than 24 hours for the encrypt key.
#
# The ticket's lifetime should be set to twice the lifetime of the encryption
# key.
readonly KEY_ROTATION='0 0,12 * * *'
# The nginx rotation interval as cron mask.
#
# This should be after the keys have been rotated (see $KEY_ROTATION). Note
# that keys are only in-use after nginx has read them. This is very important if
# you're syncing the keys within a cluster.
readonly SERVER_ROTATION='30 0,12 * * *'
# Absolute path to the web server system startup program.
readonly SERVER_INIT_PATH='/etc/init.d/nginx'
# The minimum version the server has to have for session ticket keys via files.
readonly SERVER_MIN_VERSION='1.5.7'
# The minimum version the OpenSSL library requires for session ticket support.
readonly OPENSSL_MIN_VERSION='0.9.8f'
# Absolute path to the cron program.
readonly CRON_PATH='/etc/cron.d/session_ticket_key_rotation'
# Absolute path to the temporary file system.
readonly KEY_PATH='/mnt/session_ticket_keys'
# Absolute path to the system startup program.
readonly INIT_PATH='/etc/init.d/session_ticket_keys'
# Absolute path to the `filesystems` file.
readonly FILESYSTEMS_PATH='/proc/filesystems'
# ------------------------------------------------------------------------------
# Global System Variables
# ------------------------------------------------------------------------------
# The comment that should be added to /etc/fstab for easy identification.
readonly FSTAB_COMMENT='# Volatile TLS session ticket key file system.'
# Name of the server daemon / executable.
readonly SERVER="${SERVER_INIT_PATH##*/}"
# For more information on shell colors and other text formatting see:
# http://stackoverflow.com/a/4332530/1251219
readonly RED=$(tput bold; tput setaf 1)
readonly GREEN=$(tput bold; tput setaf 2)
readonly YELLOW=$(tput bold; tput setaf 3)
readonly UNDERLINE=$(tput smul)
readonly NORMAL=$(tput sgr0)
# This variable can be checked by scripts to see if they were included.
readonly CONFIG_LOADED=true
# Whether to suppress any output.
SILENT=false
# Whether we are verbose in outputting or not.
VERBOSE=false
# ------------------------------------------------------------------------------
# Global Functions
# ------------------------------------------------------------------------------
# Add fstab entry.
#
# GLOBAL:
# $FSTAB_COMMENT - The comment that's added in the line before the entry.
# ARGS:
# $1 - The desired file system to mount.
# $2 - The file system's options
# $3 - The path to the file system.
# $4 - Absolute path to the fstab file.
# RETURN:
# 0 - Adding successful.
# 1 - Adding failed.
add_fstab_entry()
{
cat << EOT >> "${4}" || return 1
${FSTAB_COMMENT}
${1} ${3} ${1} ${2} 0 0
EOT
ok "Added ${YELLOW}${4}${NORMAL} entry"
}
# Change owner of a directory and all files in it and ensure shell scripts are
# executable by the new owner only.
#
# ARGS:
# $1 - Absolute path to the directory.
# $2 - User and group name of the new owner.
# RETURN:
# 0 - If ownership was changed successfully.
# 1 - If changing ownership failed.
change_owner_and_make_scripts_executable()
{
chown -R -- "${2}":"${2}" "${1}" || return 1
chmod -R -- 0755 "${1}" || return 1
find "${1}" -type f -exec chmod -- 0644 {} \; || return 1
find "${1}" -name '*.sh' -type f -exec chmod -- 0744 {} \; || return 1
ok "Repository files owned and executable by ${2} users only"
}
# Check available file systems for availability of a volatile one.
#
# GLOBAL:
# $FILESYSTEM - After calling this function this global variable is set to the
# volatile file system that was found on this server. This is either `ramfs`
# or `tmpfs`. The variable is set to `false` if no volatile file system could
# be found.
# ARGS:
# $1 - Absolute path to the `filesystems` file.
# RETURN:
# 0 - Available
# 1 - Not available
check_filesystem()
{
if grep -q ramfs "${1}"
then
FILESYSTEM='ramfs'
ok "Using ${YELLOW}ramfs${NORMAL}"
elif grep -q tmpfs "${1}"
then
FILESYSTEM='tmpfs'
warn "Using ${YELLOW}tmpfs${NORMAL} which means that your keys \
${UNDERLINE}might${NORMAL} hit persistent storage if you have a swap"
else
FILESYSTEM=false
fail "No support for ${YELLOW}ramfs${NORMAL} nor ${YELLOW}tmpfs${NORMAL} \
on this system"
fi
}
# Check if an ntp daemon is installed.
#
# A correctly set system clock is imperative if keys are shared in cluster.
#
# NOTE: >&- nor 1>&- works in dash!
# RETURN:
# 0 - Always
check_ntpd()
{
if type ntp >/dev/null 2>&1
then
ok "Found ${YELLOW}ntp${NORMAL}"
elif type openntpd >/dev/null 2>&1
then
ok "Found ${YELLOW}openntpd${NORMAL}"
elif type ntpdate >/dev/null 2>&1
then
warn "Found ${YELLOW}ntpdate${NORMAL} (deprecated)"
else
warn "Consider installing an ${YELLOW}ntp daemon${NORMAL} to set your \
system time and ensure all servers are in sync"
fi
}
# Check OpenSSL version which has (of course) an awkward formatting.
#
# ARGS:
# $1 - The minimum required version.
# RETURN:
# 0 - If version is equal or greater.
# 1 - If version is lower.
check_openssl_version()
{
# Example output: `OpenSSL 1.0.1f 6 Jan 2014`
OPENSSL_VERSION=$(openssl version)
OPENSSL_VERSION="${OPENSSL_VERSION#* }" # Remove smallest prefix space.
OPENSSL_VERSION="${OPENSSL_VERSION%% *}" # Remove largest suffix space.
# Now we have only `1.0.1f` left from above example.
# This one's complicated. We need an integer for -ge comparison and therefore
# remove the last character and all dots from the version string. Afterwards
# we get the last character and convert it to its ASCII code point.
#
# Note the leading single quote in front of the second command, that's what
# converts the character to its code point.
V1=$(printf -- '%s%03d' \
"$(printf -- '%s' ${OPENSSL_VERSION} | head -c -1 | tr -d '.')" \
"'$(printf -- '%s' ${OPENSSL_VERSION} | tail -c -1)")
# Now we need to do the same with the minimum version.
V2=$(printf -- '%s%03d' \
"$(printf -- '%s' ${1} | head -c -1 | tr -d '.')" \
"'$(printf -- '%s' ${1} | tail -c -1)")
# Greater or equals is what we are interested in.
if [ "${V1}" -ge "${V2}" ]
then
ok "Installed OpenSSL version is ${YELLOW}${OPENSSL_VERSION}${NORMAL}"
else
fail "Installed OpenSSL version is ${YELLOW}${OPENSSL_VERSION}${NORMAL} \
which does not support session ticket keys. You need to install at least \
version ${YELLOW}${2}${NORMAL}"
fi
}
# Check program version.
#
# NOTE: Works for nginx and Apache http (httpd).
# ARGS:
# $1 - The name of the program to check the version (must support -v option).
# $2 - The minimum version.
# RETURN:
# 0 - If version is equal or greater.
# 1 - If version is lower.
check_server_version()
{
# Get version information from program. The head call isn't necessary for
# nginx but it is for httpd because it will output something like:
# Server version: Apache/2.4.10
# Server built: Jul 09 2014 07:22:45
SERVER_VERSION=$("${1}" -v 2>&1 | head -n1)
# nginx: nginx version: nginx/1.7.6 (Ubuntu)
# httpd: Server version: Apache/2.4.10 (Ubuntu)
SERVER_VERSION="${SERVER_VERSION##*/}" # Remove longest match slash.
# nginx: 1.7.6 (Ubuntu)
# httpd: 2.4.10 (Ubuntu)
# nginx: 1.7.6 (Ubuntu)
# httpd: 2.4.10 (Ubuntu)
SERVER_VERSION="${SERVER_VERSION%% *}" # Remove longest match space.
# nginx: 1.7.6
# httpd: 2.4.10
# Remove dots.
V1=$(printf -- '%s' "${SERVER_VERSION}" | tr -d '.')
V2=$(printf -- '%s' "${2}" | tr -d '.')
# Greater or equals is what we are interested in.
if [ "${V1}" -ge "${V2}" ]
then
ok "Installed server version is ${YELLOW}${SERVER_VERSION}${NORMAL}"
else
fail "Installed server version is ${YELLOW}${SERVER_VERSION}${NORMAL} \
which does not support settings ticket keys via files. You need to install at \
least version ${YELLOW}${2}${NORMAL}"
fi
}
# Create rotation cron job.
#
# GLOBALS:
# $KEY_ROTATION - The key rotation cron mask.
# $SERVER_ROTATION - The server rotation cron mask.
# $SERVER - The name of the server daemon.
# ARGS:
# $1 - Absolute path to the cron file.
# $2 - Absolute path to the rotation script.
# $3 - The server names that should be passed to the rotation script.
# RETURN:
# 0 - Creation successful.
# 1 - Creation failed.
create_cron_job()
{
cat << EOT > "${1}" || return 1
# ------------------------------------------------------------------------------
# TLS session ticket key rotation.
#
# LINK: https://github.com/Fleshgrinder/nginx-session-ticket-key-rotation
# ------------------------------------------------------------------------------
${KEY_ROTATION} sh -- '${2}' ${3}
${SERVER_ROTATION} service ${SERVER} reload
EOT
ok "Created cron rotation job ${YELLOW}${1}${NORMAL}"
}
# Create directory and ensure it's only accessible by given user and group.
#
# ARGS:
# $1 - Absolute path to the directory that should be created.
# $2 - User and group name of the owner.
# RETURN:
# 0 - If directory was successfully created.
# 1 - If creation of directory failed.
create_directory()
{
mkdir -p -- "${1}" || return 1
chmod -- 0550 "${1}" || return 1
chown -- "${2}":"${2}" "${1}" || return 1
ok "Created directory ${YELLOW}${1}${NORMAL}"
}
# Create init dependency.
#
# ARGS:
# $1 - Absolute path to the init script (the dependency).
# $2 - Absolute path to the depending init script.
# RETURN:
# 0 - Creation successful.
# 1 - Creation failed.
create_init_dependency()
{
sed -i -- "/# Required-Start:/ s/\$/ \$${1##*/}/" "${2}" || return 1
ok "Created system startup dependency in ${YELLOW}${2}${NORMAL}"
}
# Create init script links.
#
# NOTE: The runlevels are fixed to boot only and sequence to 10.
# ARGS:
# $1 - Absolute path to the init script to create links for.
# RETURN:
# 0 - Creation successful.
# 1 - Creation failed.
create_init_links()
{
update-rc.d "${1##*/}" start 10 2 3 4 5 . >/dev/null 2>&1 || return 1
ok "Created system startup links for ${YELLOW}${1}${NORMAL}"
}
# Create init script.
#
# NOTE: The runlevels are fixed to boot only.
# ARGS:
# $1 - Absolute path to the init script.
# $2 - Absolute path to the key generation script.
# $3 - The server names that should be passed to the key generation script.
# RETURN:
# 0 - Creation successful.
# 1 - Creation failed.
create_init_script()
{
DAEMON_NAME="${1##*/}"
cat << EOT > "${1}" || return 1
#!/bin/sh
### BEGIN INIT INFO
# Provides: ${DAEMON_NAME}
# Required-Start: \$local_fs \$syslog
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Generates random TLS session ticket keys on boot.
# Description:
# The script will generate random TLS session ticket keys for all servers that
# were defined during the installation of the program. The web server service
# should specify this script as a dependency, this ensures that keys are
# available on boot.
### END INIT INFO
# ------------------------------------------------------------------------------
# TLS session ticket key rotation.
#
# LINK: https://github.com/Fleshgrinder/nginx-session-ticket-key-rotation
# ------------------------------------------------------------------------------
sh '${2}' ${3}
EOT
chown -- root:root "${1}" || return 1 # Init scripts always belong to root.
chmod -- 0755 "${1}" || return 1 # 0755 is the default for init scripts.
ok "Created system startup program ${YELLOW}${1}${NORMAL} to generate keys on boot"
}
# Display fail message and exit program.
#
# ARGS:
# $1 - The message's text.
# RETURN:
# 1 - Always
fail()
{
if [ "${SILENT}" = false ]
then
printf "[%sfail%s] %s.\n" "${RED}" "${NORMAL}" "${1}" >&2
fi
return 1
}
# Generate (48 byte) random session ticket key.
#
# LINK: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dd.html#tag_20_31
# ARGS:
# $1 - Absolute path to the key file.
# RETURN:
# 0 - If key generation was successful.
# 1 - If key generation failed.
generate_key()
{
dd 'if=/dev/random' "of=${1}" 'bs=1' 'count=48' 2>/dev/null || return 1
}
# Generate random keys for all servers.
#
# GLOBALS:
# $KEY_PATH - Absolute path to the directory where the keys should be stored.
# ARGS:
# $@ - The server names to generate keys for.
# RETURN:
# 0 - Generation of all keys was successful.
# 1 - Generation of keys failed.
generate_keys()
{
[ "${VERBOSE}" = true ] && printf -- 'Generating random keys ...\n'
for SERVER_NAME in ${@}
do
# Copy 2 over 3 and 1 over 2.
for KEY in 2 1
do
OLD_KEY="${KEY_PATH}/${SERVER_NAME}.${KEY}.key"
NEW_KEY="${KEY_PATH}/${SERVER_NAME}.$(( ${KEY} + 1 )).key"
# Only perform copy operation if we actually have something to copy,
# otherwise create file with random data to avoid web server errors. Note
# that those files can't be used to decrypt anything, they are simple seed
# data.
if [ -f "${OLD_KEY}" ]
then
cp -- "${OLD_KEY}" "${NEW_KEY}"
ok "Copied ${YELLOW}${OLD_KEY}${NORMAL} over ${YELLOW}${NEW_KEY}${NORMAL}"
else
generate_key "${NEW_KEY}"
ok "Newly generated ${YELLOW}${NEW_KEY}${NORMAL}"
fi
done
ENCRYPTION_KEY="${KEY_PATH}/${SERVER_NAME}.1.key"
generate_key "${ENCRYPTION_KEY}"
ok "Generated new encryption key ${YELLOW}${ENCRYPTION_KEY}${NORMAL}"
done
[ "${VERBOSE}" = true ] && printf -- 'Key generation finished!\n'
return 0
}
# Check if given software is installed.
#
# ARGS:
# $1 - The software to check.
# RETURN:
# 0 - Installed
# 1 - Not installed
is_installed()
{
if type "${1}" >/dev/null 2>&1
then
ok "${YELLOW}${1}${NORMAL} is installed"
else
fail "${YELLOW}${1}${NORMAL} does not seem to be installed"
fi
}
# Mount file system.
#
# ARGS:
# $1 - The desired file system to mount.
# $2 - The file system's options
# $3 - The path to the file system.
# RETURN:
# 0 - Mounting successful.
# 1 - Mounting failed.
mount_filesystem()
{
mount -t "${1}" -o "${2}" -- "${1}" "${3}" || return 1
ok "Mounted ${YELLOW}${1}${NORMAL} on ${YELLOW}${3}${NORMAL}"
}
# Display ok message and continue program.
#
# Note that an ok message is only printed in verbose mode and if not silent.
#
# ARGS:
# $1 - The message's text.
# RETURN:
# 0 - Message was printed to `stdout`
# 1 - Printing of message failed.
ok()
{
if [ "${VERBOSE}" = true ] && [ "${SILENT}" = false ]
then
printf -- "[ %sok%s ] %s ...\n" "${GREEN}" "${NORMAL}" "${1}"
fi
}
# Check if super user is executing the program.
#
# RETURN:
# 0 - Super user
# 1 - No super user
super_user()
{
UID=$(id -u)
if [ "${UID}" -eq 0 ]
then
ok 'root (sudo)'
else
fail 'Program must be executed as root (sudo)'
fi
}
# Uninstall TLS session ticket key rotation.
#
# TODO: Split into reusable, smaller, testable functions.
# RETURN:
# 0 - Uninstalled
# 1 - Failure
uninstall()
{
[ "${VERBOSE}" = true ] && printf -- 'Uninstalling ...\n'
INIT_NAME="${INIT_PATH##*/}"
if grep -q -- " \$${INIT_NAME}" "${SERVER_INIT_PATH}"
then
sed -i -- "s/ \$${INIT_NAME}//g" "${SERVER_INIT_PATH}"
ok "Removed system startup dependency in ${YELLOW}${SERVER_INIT_PATH}${NORMAL}"
else
ok "System startup dependency already removed in ${YELLOW}${SERVER_INIT_PATH}${NORMAL}"
fi
update-rc.d -f "${INIT_NAME}" remove >/dev/null 2>&1
ok "Removed any system startup links for ${YELLOW}${INIT_PATH}${NORMAL}"
if [ -f "${INIT_PATH}" ]
then
rm -- "${INIT_PATH}"
ok "Removed system startup program ${YELLOW}${INIT_PATH}${NORMAL}"
else
ok "System startup program ${YELLOW}${INIT_PATH}${NORMAL} already removed"
fi
if [ -f "${CRON_PATH}" ]
then
rm -- "${CRON_PATH}"
ok "Removed cron program ${YELLOW}${CRON_PATH}${NORMAL}"
else
ok "Cron program ${YELLOW}${CRON_PATH}${NORMAL} already removed"
fi
if grep -q -- "${FSTAB_COMMENT}" /etc/fstab
then
sed -i -- "/${FSTAB_COMMENT}/,+1 d" '/etc/fstab'
ok "Removed ${YELLOW}/etc/fstab${NORMAL} entry"
else
ok "No entry found in ${YELLOW}/etc/fstab${NORMAL}"
fi
if grep -q -- "${KEY_PATH}" /proc/mounts
then
umount -fl -- "${KEY_PATH}"
ok "Unmounted ${YELLOW}${KEY_PATH}${NORMAL}"
else
ok "${YELLOW}${KEY_PATH}${NORMAL} already unmounted"
fi
if [ -d "${KEY_PATH}" ]
then
rmdir -- "${KEY_PATH}"
ok "Removed directory ${YELLOW}${KEY_PATH}${NORMAL}"
else
ok "Directory ${YELLOW}${KEY_PATH}${NORMAL} does not exist"
fi
[ "${VERBOSE}" = true ] && printf -- 'Uninstallation finished!\n'
return 0
}
# Print usage text.
#
# GLOBAL:
# $ARGUMENTS - Program argument description.
# $DESCRIPTION - Description what the program does.
# RETURN:
# 0 - Printing successful.
# 1 - Printing failed.
usage()
{
cat << EOT
Usage: ${0##*/} [OPTION]... ${ARGUMENTS}
${DESCRIPTION}
-h Display this help and exit.
-s Be silent and do not print any message.
-v Print message for each successful command.
Report bugs to [email protected]
GitHub repository: https://github.com/Fleshgrinder/nginx-session-ticket-key-rotation
For complete documentation, see: README.md
EOT
}
# Display warn message and continue program.
#
# Note that a warn message is only printed if not silent.
#
# ARGS:
# $1 - The message's text.
# RETURN:
# 0 - Message was printed to `stdout`
# 1 - Printing of message failed.
warn()
{
if [ "${SILENT}" = false ]
then
printf "[%swarn%s] %s ...\n" "${YELLOW}" "${NORMAL}" "${1}"
fi
}
# ------------------------------------------------------------------------------
# Handle Options
# ------------------------------------------------------------------------------
# Check for possibly passed options.
while getopts 'hsv' OPT
do
case "${OPT}" in
h) usage && exit 0 ;;
s) SILENT=true ;;
v) VERBOSE=true ;;
*) usage 2>&1 && exit 1 ;;
esac
# We have to remove found options from the input for later evaluations of
# passed arguments in subscripts that are not interested in these options.
shift $(( $OPTIND - 1 ))
done