tinycm diff index.cgi @ rev 5
Add tinycm CGI script
author | Christophe Lincoln <pankso@slitaz.org> |
---|---|
date | Wed Apr 11 15:59:21 2012 +0200 (2012-04-11) |
parents | |
children | 9d0f7be12384 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/index.cgi Wed Apr 11 15:59:21 2012 +0200 1.3 @@ -0,0 +1,612 @@ 1.4 +#!/bin/sh 1.5 +# 1.6 +# TinyCM - Small, fast and elegent CGI/SHell Content Manager 1.7 +# 1.8 +# Copyright (C) 2012 SliTaz GNU/Linux - BSD License 1.9 +# 1.10 +. /usr/lib/slitaz/httphelper 1.11 + 1.12 +# Let have a peer site config file with a .cgi extension so content 1.13 +# is secure even if left in a web server directory. 1.14 +. config.cgi 1.15 + 1.16 +tiny="$PWD" 1.17 +po="en fr" 1.18 +content="content" 1.19 +wiki="$content/wiki" 1.20 +index="index" 1.21 +cache="cache" 1.22 +plugins="plugins" 1.23 +tmp="/tmp/tinycm" 1.24 +sessions="$tmp/sessions" 1.25 +script=$SCRIPT_NAME 1.26 + 1.27 +# Content negotiation for Gettext 1.28 +IFS="," 1.29 +for lang in $HTTP_ACCEPT_LANGUAGE 1.30 +do 1.31 + lang=${lang%;*} lang=${lang# } lang=${lang%-*} 1.32 + if echo "$po" | fgrep -q "$lang"; then 1.33 + break 1.34 + fi 1.35 + case "$lang" in 1.36 + en) lang="C" ;; 1.37 + fr) lang="fr_FR" ;; 1.38 + pt) lang="pt_BR" ;; 1.39 + ru) lang="ru_RU" ;; 1.40 + esac 1.41 +done 1.42 +unset IFS 1.43 +export LANG=$lang LC_ALL=$lang 1.44 + 1.45 +# 1.46 +# Functions 1.47 +# 1.48 + 1.49 +# Used by edit to display language name and the language box. this is 1.50 +# for CM content not gettext support. 1.51 +get_lang() { 1.52 + lang=$(echo $d | cut -d "/" -f 1) 1.53 + doc=${d#$lang/} 1.54 + echo '<div id="lang">' 1.55 + for l in $LANGUAGES 1.56 + do 1.57 + case $lang in 1.58 + en) i18n="English" ;; 1.59 + fr) i18n="Français" ;; 1.60 + pt) i18n="Português" ;; 1.61 + ru) i18n="Русский" ;; 1.62 + *) i18n="*" ;; 1.63 + esac 1.64 + echo "<a href='?d=$l/$doc'>$l</a>" 1.65 + done 1.66 + echo '</div>' 1.67 +} 1.68 + 1.69 +# HTML 5 header. 1.70 +html_header() { 1.71 + if [ -f "$tiny/lib/header.html" ]; then 1.72 + cat $tiny/lib/header.html | sed -e s!'%TITLE%'!"$TITLE - $d"!g 1.73 + else 1.74 + cat << EOT 1.75 +<!DOCTYPE html> 1.76 +<html xmlns="http://www.w3.org/1999/xhtml"> 1.77 +<head> 1.78 + <title>$TITLE</title> 1.79 + <meta charset="utf-8" /> 1.80 + <style type="text/css">body { margin: 40px 120px; }</style> 1.81 +</head> 1.82 +<body> 1.83 +<!-- Content --> 1.84 +<div id="content"> 1.85 +EOT 1.86 + fi 1.87 +} 1.88 + 1.89 +# HTML 5 footer. 1.90 +html_footer() { 1.91 + if [ -f "$tiny/lib/footer.html" ]; then 1.92 + cat $tiny/lib/footer.html 1.93 + else 1.94 + cat << EOT 1.95 + 1.96 +<!-- End content --> 1.97 +</div> 1.98 + 1.99 +<div id="footer">♥</div> 1.100 + 1.101 +</body> 1.102 +</html> 1.103 +EOT 1.104 + fi 1.105 +} 1.106 + 1.107 +# Default index if missing 1.108 +default_index() { 1.109 + mkdir -p $(dirname $index) 1.110 + cat > $wiki/$index.txt << EOT 1.111 +==== Welcome ==== 1.112 + 1.113 +This is the default index page of your CM, you can edit to start adding 1.114 +some content to your TinyCM. 1.115 + 1.116 +EOT 1.117 +} 1.118 + 1.119 +# Log documents activity. 1.120 +log() { 1.121 + grep ^[A-Z] | \ 1.122 + sed s"#^[A-Z]\([^']*\)#$(date '+%Y-%m-%d %H:%M') : \0#" \ 1.123 + >> $cache/$d/activity.log 1.124 +} 1.125 + 1.126 +# Check if user is auth 1.127 +check_auth() { 1.128 + auth="$(COOKIE auth)" 1.129 + user="$(echo $auth | cut -d ":" -f 1)" 1.130 + md5cookie="$(echo $auth | cut -d ":" -f 2)" 1.131 + [ -f "$sessions/$user" ] && md5session="$(cat $sessions/$user)" 1.132 + if [ "$md5cookie" == "$md5session" ] && [ "$auth" ]; then 1.133 + . $PEOPLE/$user/account.conf 1.134 + return 0 1.135 + else 1.136 + return 1 1.137 + fi 1.138 +} 1.139 + 1.140 +# Authentified or not 1.141 +user_box() { 1.142 + if check_auth; then 1.143 + cat << EOT 1.144 + 1.145 +<div id="user"> 1.146 + <a href="$script?user=$user">$(get_gravatar $MAIL 20)</a> 1.147 + <a href="$script?logout">Logout</a> 1.148 +</div> 1.149 + 1.150 +EOT 1.151 + else 1.152 + cat << EOT 1.153 + 1.154 +<div id="user"> 1.155 + <a href="$script?login"><img src="images/avatar.png" alt="[ User ]" /></a> 1.156 + <a href="$script?login">Login</a> 1.157 +</div> 1.158 + 1.159 +EOT 1.160 + fi 1.161 + cat << EOT 1.162 +<!-- 1.163 +<div id="search"> 1.164 + <form method="get" action="$script"> 1.165 + <input type="text" name="search" placeholder="$(gettext "Search")" /> 1.166 + </form> 1.167 +</div> 1.168 +--> 1.169 +EOT 1.170 +} 1.171 + 1.172 +# Link for online signup if enabled. 1.173 +online_signup() { 1.174 + if [ "$ONLINE_SIGNUP" == "yes" ]; then 1.175 + echo -n "<p><a href='$script?signup'>" 1.176 + gettext "Create a new account" 1.177 + echo '</a></p>' 1.178 + fi 1.179 +} 1.180 + 1.181 +# Login page 1.182 +login_page() { 1.183 + cat << EOT 1.184 +<h2>$(gettext "Login")</h2> 1.185 + 1.186 +<div id="account-info"> 1.187 +$(gettext "Not yet and account or trouble with you account? Please send 1.188 +a request to $ADMIN_MAIL with your real name, user name, mail and password.") 1.189 +$(online_signup) 1.190 +</div> 1.191 + 1.192 +<div id="login"> 1.193 + <form method="post" action="$script"> 1.194 + <input type="text" name="auth" placeholder="$(gettext "User name")" /> 1.195 + <input type="password" name="pass" placeholder="$(gettext "Password")" /> 1.196 + <div> 1.197 + <input type="submit" value="Login" /> $error 1.198 + </div> 1.199 + </form> 1.200 +</div> 1.201 + 1.202 +<div style="clear: both;"></div> 1.203 +EOT 1.204 +} 1.205 + 1.206 +# Signup page 1.207 +signup_page() { 1.208 + cat << EOT 1.209 + 1.210 +<div id="signup"> 1.211 + <form method="post" name="signup" action="$script" onsubmit="return checkSignup();"> 1.212 + <input type="hidden" name="signup" value="new" /> 1.213 + <input type="text" name="name" placeholder="$(gettext "Real name")" /> 1.214 + <input type="text" name="user" placeholder="$(gettext "User name")" /> 1.215 + <input type="text" name="mail" placeholder="$(gettext "Email")" /> 1.216 + <input type="password" name="pass" placeholder="$(gettext "Password")" /> 1.217 + <div> 1.218 + <input type="submit" value="$(gettext "Create new account")" /> 1.219 + </div> 1.220 + </form> 1.221 +</div> 1.222 + 1.223 +EOT 1.224 +} 1.225 + 1.226 +# Create a new user in AUTH_FILE and PEOPLE 1.227 +new_user_config() { 1.228 + key=$(echo -n "$user:$mail:$pass" | md5sum | awk '{print $1}') 1.229 + echo "$user:$pass" >> $AUTH_FILE 1.230 + mkdir -p $PEOPLE/$user/ 1.231 + cat > $PEOPLE/$user/account.conf << EOT 1.232 +# SliTaz user configuration 1.233 +# 1.234 + 1.235 +NAME="$name" 1.236 +USER="$user" 1.237 +MAIL="$mail" 1.238 +KEY="$key" 1.239 + 1.240 +EOT 1.241 + chmod 0600 $PEOPLE/$user/account.conf 1.242 +} 1.243 + 1.244 +# Display user public profile. 1.245 +public_people() { 1.246 + cat << EOT 1.247 +<pre> 1.248 +Real name : $NAME 1.249 +</pre> 1.250 +EOT 1.251 +} 1.252 + 1.253 +# Display authentified user profile. TODO: change password 1.254 +auth_people() { 1.255 + cat << EOT 1.256 +<pre> 1.257 +Real name : $NAME 1.258 +Email : $MAIL 1.259 +Secure key : $KEY 1.260 +</pre> 1.261 +EOT 1.262 +} 1.263 + 1.264 +# The CM style parser. Just title, simple text formating and internal 1.265 +# link, as well as images and use HTML for other stuff. Keep it fast! 1.266 +# To make TinyCM as easy as possible we have a small HTML editor/helper 1.267 +# written in Javascript 1.268 +wiki_parser() { 1.269 + doc="[0-9a-zA-Z\.\#/~\_%=\?\&,\+\:@;!\(\)\*\$'\-]*" 1.270 + sed \ 1.271 + -e s"#====\([^']*\)====#<h2>\1</h2>#"g \ 1.272 + -e s"#===\([^']*\)===#<h3>\1</h3>#"g \ 1.273 + -e s"#==\([^']*\)==#<h4>\1</h4>#"g \ 1.274 + -e s"#\*\*\([^']*\)\*\*#<strong>\1</strong>#"g \ 1.275 + -e s"#''\([^']*\)''#<em>\1</em>#"g \ 1.276 + -e s"#__\([^']*\)__#<u>\1</u>#"g \ 1.277 + -e s"#\[\([^]]*\)|\($doc\)\]#<a href='$script?d=\2'>\1</a>#"g \ 1.278 + -e s"#http://\([^']*\).png#<img src='\0' />#"g \ 1.279 + -e s"#http://\([^']*\).*# <a href='\0'>\1</a>#"g 1.280 + #-e s"/^$/<br \/>/"g 1.281 +} 1.282 + 1.283 +link_user() { 1.284 + echo "<a href='$script?user=$user'>$user</a>" 1.285 +} 1.286 + 1.287 +# Save a document. Do we need more than 1 backup and diff ? 1.288 +save_document() { 1.289 + mkdir -p $cache/$d $(dirname $wiki/$d) 1.290 + # May be a new page. 1.291 + if [ ! -f "$wiki/$d.txt" ]; then 1.292 + new=0 1.293 + touch $wiki/$d.txt 1.294 + fi 1.295 + cp $wiki/$d.txt $cache/$d/last.bak 1.296 + sed "s/$(echo -en '\r') /\n/g" > $wiki/$d.txt << EOT 1.297 +$(GET content) 1.298 +EOT 1.299 + diff $cache/$d/last.bak $wiki/$d.txt > $cache/$d/last.diff 1.300 + # Log 1.301 + if [ "$new" ]; then 1.302 + echo "Page created by: $(link_user)" | log 1.303 + if [ "$HG" == "yes" ]; then 1.304 + cd $content && hg -q add 1.305 + hg commit -q -u "$NAME <$MAIL>" -m "Created new document: $d" 1.306 + cd $tiny 1.307 + fi 1.308 + else 1.309 + # Here we will clean log: cat && tail -n 40 1.310 + echo "Page edited by: $(link_user)" | log 1.311 + if [ "$HG" == "yes" ]; then 1.312 + cd $content && hg commit -q -u "$NAME <$MAIL>" \ 1.313 + -m "Edited document: $d" 1.314 + cd $tiny 1.315 + fi 1.316 + fi 1.317 +} 1.318 + 1.319 +# CM tools (edit, diff, etc). 1.320 +wiki_tools() { 1.321 + cat << EOT 1.322 +<div id="tools"> 1.323 + <a href="$script?edit=$d">$(gettext "Edit document")</a> 1.324 + <a href="$script?diff=$d">$(gettext "Last diff")</a> 1.325 + <a href="$script?log=$d">$(gettext "Activity")</a> 1.326 + <a href="$script?dashboard">Dashboard</a> 1.327 + $([ "$HG" == "yes" ] && echo "<a href='$script?hg'>Hg Log</a>") 1.328 +</div> 1.329 +EOT 1.330 +} 1.331 + 1.332 +# Get and display Gravatar image: get_gravatar email size 1.333 +# Link to profile: <a href="http://www.gravatar.com/$md5">...</a> 1.334 +get_gravatar() { 1.335 + email=$1 1.336 + size=$2 1.337 + [ "$size" ] || size=48 1.338 + url="http://www.gravatar.com/avatar" 1.339 + md5=$(md5crypt $email) 1.340 + echo "<img src='$url/$md5?d=identicon&s=$size' alt='∗' />" 1.341 +} 1.342 + 1.343 +# List hg logs 1.344 +hg_log() { 1.345 + cd $content 1.346 + cat << EOT 1.347 +<table> 1.348 + <thead> 1.349 + <td>$(gettext "User")</td> 1.350 + <td>$(gettext "Description")</td> 1.351 + <td>$(gettext "Revision")</td> 1.352 + </thead> 1.353 +EOT 1.354 + hg log --template "<tr><td>{author}</td><td>{desc}</td><td>{rev}</td></tr>\n" 1.355 + echo '</table>' 1.356 +} 1.357 + 1.358 +# 1.359 +# POST actions 1.360 +# 1.361 + 1.362 +case " $(POST) " in 1.363 + *\ auth\ *) 1.364 + # Authenticate user. Create a session file in $sessions to be used 1.365 + # by check_auth. We have the user login name and a peer session 1.366 + # md5 string in the COOKIE. 1.367 + user="$(POST auth)" 1.368 + pass="$(md5crypt "$(POST pass)")" 1.369 + valid=$(fgrep "${user}:" $AUTH_FILE | cut -d ":" -f 2) 1.370 + if [ "$pass" == "$valid" ] && [ "$pass" != "" ]; then 1.371 + md5session=$(echo -n "$$:$user:$pass:$$" | md5sum | awk '{print $1}') 1.372 + [ -d $sessions ] || mkdir -p $sessions 1.373 + echo "$md5session" > $sessions/$user 1.374 + header "Location: $script" \ 1.375 + "Set-Cookie: auth=$user:$md5session; HttpOnly" 1.376 + else 1.377 + header "Location: $script?login&error" 1.378 + fi ;; 1.379 + *\ signup\ *) 1.380 + # POST action for signup 1.381 + name="$(POST name)" 1.382 + user="$(POST user)" 1.383 + mail="$(POST mail)" 1.384 + pass="$(md5crypt "$(POST pass)")" 1.385 + if ! grep "^${user}:" $AUTH_FILE; then 1.386 + new_user_config 1.387 + header "Location: $script?login" 1.388 + else 1.389 + header 1.390 + html_header 1.391 + user_box 1.392 + echo "<h2>gettext "User already exist: $user"</h2>" 1.393 + html_footer 1.394 + fi ;; 1.395 +esac 1.396 + 1.397 +# 1.398 +# Plugins 1.399 +# 1.400 +for p in $(ls -1 $plugins) 1.401 +do 1.402 + [ -f "$plugins/$p/$p.conf" ] && . $plugins/$p/$p.conf 1.403 + [ -x "$plugins/$p/$p.cgi" ] && . $plugins/$p/$p.cgi 1.404 +done 1.405 + 1.406 +# 1.407 +# GET actions 1.408 +# 1.409 + 1.410 +case " $(GET) " in 1.411 + *\ edit\ *) 1.412 + d="$(GET edit)" 1.413 + header 1.414 + html_header 1.415 + user_box 1.416 + get_lang 1.417 + if check_auth; then 1.418 + get_lang 1.419 + cat << EOT 1.420 +<h2>$(gettext "Edit $doc [ $i18n ]")</h2> 1.421 + 1.422 +<div id="edit"> 1.423 + 1.424 +<form method="get" action="$script" name="editor"> 1.425 + <input type="hidden" name="save" value="$d" /> 1.426 + <textarea name="content">$(cat "$wiki/$d.txt")</textarea> 1.427 + <input type="submit" value="$(gettext "Save document")" /> 1.428 + $(gettext "Code Helper:") 1.429 + $(cat lib/jseditor.html) 1.430 +</form> 1.431 + 1.432 +</div> 1.433 +EOT 1.434 + else 1.435 + gettext "You must be logged to edit pages" 1.436 + fi 1.437 + html_footer ;; 1.438 + *\ save\ *) 1.439 + d="$(GET save)" 1.440 + if check_auth; then 1.441 + save_document 1.442 + fi 1.443 + header "Location: $script?d=$d" ;; 1.444 + *\ log\ *) 1.445 + d="$(GET log)" 1.446 + header 1.447 + html_header 1.448 + user_box 1.449 + get_lang 1.450 + echo "<h2>$(gettext "Activity for:") <a href='$script?d=$d'>$d</a></h2>" 1.451 + echo '<pre>' 1.452 + if [ -f "$cache/$d/activity.log" ]; then 1.453 + tac $cache/$d/activity.log 1.454 + else 1.455 + gettext "No log for: $d"; echo 1.456 + fi 1.457 + echo '</pre>' 1.458 + if check_auth; then 1.459 + wiki_tools 1.460 + fi 1.461 + html_footer ;; 1.462 + *\ diff\ *) 1.463 + d="$(GET diff)" 1.464 + date="last" 1.465 + header 1.466 + html_header 1.467 + user_box 1.468 + get_lang 1.469 + echo "<h2>$(gettext "Diff for:") <a href='$script?d=$d'>$d</a></h2>" 1.470 + echo '<pre>' 1.471 + if [ -f "$cache/$d/$date.diff" ]; then 1.472 + cat $cache/$d/$date.diff | sed \ 1.473 + -e 's|&|\&|g' -e 's|<|\<|g' -e 's|>|\>|g' \ 1.474 + -e s"#^-\([^']*\).#<span style='color: red;'>\0</span>#"g \ 1.475 + -e s"#^+\([^']*\).#<span style='color: green;'>\0</span>#"g \ 1.476 + -e s"#@@\([^']*\)@@#<span style='color: blue;'>@@\1@@</span>#"g 1.477 + else 1.478 + gettext "No diff for: $d"; echo 1.479 + fi 1.480 + echo '</pre>' 1.481 + if check_auth; then 1.482 + wiki_tools 1.483 + fi 1.484 + html_footer ;; 1.485 + *\ login\ *) 1.486 + # The login page 1.487 + d="Login" 1.488 + [ "$(GET error)" ] && \ 1.489 + error="<p class="error">$(gettext "Bad login or pass")</p>" 1.490 + header 1.491 + html_header 1.492 + user_box 1.493 + login_page 1.494 + html_footer ;; 1.495 + *\ signup\ *) 1.496 + # The login page 1.497 + d="$(gettext "Sign Up")" 1.498 + header 1.499 + html_header 1.500 + user_box 1.501 + echo "<h2>$d</h2>" 1.502 + if [ "$ONLINE_SIGNUP" == "yes" ]; then 1.503 + signup_page 1.504 + else 1.505 + gettext "Online registration is disable" 1.506 + fi 1.507 + html_footer ;; 1.508 + *\ logout\ *) 1.509 + # Set a Cookie in the past to logout. 1.510 + expires="Expires=Wed, 01-Jan-1980 00:00:00 GMT" 1.511 + if check_auth; then 1.512 + rm -f "$sessions/$user" 1.513 + header "Location: $script" "Set-Cookie: auth=none; $expires; HttpOnly" 1.514 + fi ;; 1.515 + *\ user\ *) 1.516 + # User profile 1.517 + header 1.518 + html_header 1.519 + user_box 1.520 + . $PEOPLE/"$(GET user)"/account.conf 1.521 + echo "<h2>$(get_gravatar $MAIL) $(GET user)</h2>" 1.522 + loglines=$(fgrep $user $(find $cache -name *.log) | wc -l) 1.523 + gettext "Activities:"; echo " $loglines" 1.524 + if check_auth && [ "$(GET user)" == "$user" ]; then 1.525 + auth_people 1.526 + else 1.527 + public_people 1.528 + fi 1.529 + html_footer ;; 1.530 + *\ dashboard\ *) 1.531 + # For now simply list plugins and users info. We could have a 1.532 + # dashbord only for ADMINS found in the config file. The dashboard 1.533 + # should also be a plugin. 1.534 + d="Dashboard" 1.535 + header 1.536 + html_header 1.537 + user_box 1.538 + users=$(ls -1 $PEOPLE | wc -l) 1.539 + docs=$(find $wiki -type f | wc -l) 1.540 + size="$(du -sh $wiki | awk '{print $1}')" 1.541 + echo "<h2>$d</h2>" 1.542 + if check_auth; then 1.543 + echo "<p>$(gettext "Users:") $users</p>" 1.544 + echo "<p>$(gettext "Documents:") $docs ($size)</p>" 1.545 + echo "<h3>$(gettext "Plugins")</h3>" 1.546 + echo '<pre>' 1.547 + for p in $(ls -1 $plugins) 1.548 + do 1.549 + . $plugins/$p/$p.conf 1.550 + echo "<a href='?$p'>$PLUGIN</a> - $SHORT_DESC" 1.551 + done 1.552 + echo '</pre>' 1.553 + else 1.554 + gettext "You must be logged to view the dashboard." 1.555 + fi 1.556 + html_footer ;; 1.557 + *\ hg\ *) 1.558 + header 1.559 + [ "$HG" != "yes" ] && gettext "Hg is disabled" && exit 0 1.560 + [ ! -x /usr/bin/hg ] && gettext "Hg is not installed" && exit 0 1.561 + d="Hg Log" 1.562 + html_header 1.563 + user_box 1.564 + echo "<h2>$d</h2>" 1.565 + case " $(GET hg) " in 1.566 + *\ init\ *) 1.567 + if check_auth; then 1.568 + [ -d "$content/.hg" ] && exit 0 1.569 + echo '<pre>' 1.570 + gettext "Executing: hg init"; echo 1.571 + cd $content/ && hg init 1.572 + echo '[hooks]' > .hg/hgrc 1.573 + echo 'incoming = hg update' >> .hg/hgrc 1.574 + gettext "Adding current content and commiting"; echo 1.575 + [ ! -f "$wiki/index.txt" ] && touch $wiki/$index.txt 1.576 + hg add && hg commit -u "$NAME <$MAIL>" \ 1.577 + -m "Initial commit with curent content" 1.578 + echo '</pre>' && cd .. 1.579 + fi ;; 1.580 + esac 1.581 + hg_log 1.582 + html_footer ;; 1.583 + *) 1.584 + # Display requested page 1.585 + d="$(GET d)" 1.586 + [ "$d" ] || d=$index 1.587 + header 1.588 + html_header 1.589 + user_box 1.590 + get_lang 1.591 + [ ! -f "$wiki/$index.txt" ] && default_index 1.592 + if [ ! -f "$wiki/$d.txt" ]; then 1.593 + echo "<h2>$d</h2>" 1.594 + gettext "The document does not exist. You can create it or read the" 1.595 + echo " <a href='?d=help'>help</a>" 1.596 + else 1.597 + if fgrep NOWIKI $wiki/$d.txt; then 1.598 + cat $wiki/$d.txt 1.599 + else 1.600 + cat $wiki/$d.txt | wiki_parser 1.601 + fi 1.602 + fi 1.603 + if check_auth; then 1.604 + wiki_tools 1.605 + if [ "$HG" == "yes" ] && [ ! -d "$content/.hg" ]; then 1.606 + echo '<p class="error box">' 1.607 + gettext "Mercurial is enabled but no repository found" 1.608 + echo ": <a href='?hg=init'>Hg init</a>" 1.609 + echo '</p>' 1.610 + fi 1.611 + fi 1.612 + html_footer ;; 1.613 +esac 1.614 + 1.615 +exit 0