Archiv der Kategorie: Webdev

SSH-Login ohne Passwort

Ziel ist das (sichere) Anmelden per SSH von Host A (Benutzer a) an Host B (Benutzer b), allerdings ohne Passwort. Zuerst den SSH-Schlüssel erstellen (auf Rechner A):

a@A: $ ssh-keygen -C "$(whoami)@$(hostname)-$(date -I)"
a@A: $ ssh-add -l

Sollte auf Host B noch kein Verzeichnis ~/.ssh vorhanden sein, dies erstellen:

a@A: $ ssh b@B mkdir .ssh

Den gerade erstellten öffentlichen Schlüssel nach Host B kopieren:

a@A: $ cat .ssh/id_rsa.pub | ssh b@B 'cat >> .ssh/authorized_keys'

Nun sollte eine Anmeldung ohne Passwort möglich sein:

a@A: $ ssh b@B

Weiterführende Links:

Working with VIM

Vor diesem Editor habe ich mich lange Zeit gesträubt: VIM (vi improved). Die Einarbeitung ist doch etwas schwierig, da die Benutzung sehr auf Tastatur ausgelegt ist. Doch wenn die grundsätzlichen Befehle (und vor Allem die zugrundelegende Logik) bekannt ist, gelingt ein sehr flüssiges Arbeiten. Hauptsächlich arbeite ich ungern mit der Maus und gern im Terminal/Konsole. Folgend nun einige Tutorials bzw. nützliche Plugins.

VIM arbeitet in mehreren Modi, deren wichtigste sind: Normal-Modus (ansehen, ersetzen), Einfügemodus (bearbeiten), Visueller Modus (selektieren) Command-line-Modus (Angabe von Ex-Kommandos). Hinzugefügt werden kann Text grundsätzlich nur im Einfügemodus (zu Erreichen durch i [Bearbeiten vor dem Cursor] oder a [hinter dem Cursor] oder o [unter dem Cursor]).

Eines der wichtigsten Editierfunktionen ist sicherlich das Kopieren oder Verschieben von Text. Durch v (zeichenweise) oder V (zeilenweise) in den visuellen Modus wechseln und den gewünschten Text mit den Cursortasten markieren.

yy => copy current line
y => copy selected text („v“)
d => cut/delete selected text („v“)
p => paste

Weitere Infos dazu finden sich auf vim.wikia.com, tech-recipes.com oder alvinalexander.com.

Cut & Paste

Dateien aus der Betriebssystem-Zwischenablage lassen sich folgendermaßen einfügen:

  1. :set paste autoindent verhindern
  2. i in Einfüge-Modus wechseln
  3. rechte Maustaste -> Einfügen

Um Texte aus VIM in andere Programme zu kopieren, muss VIM mit der Option clipboard installiert/kompiliert sein. Dies ist standardmäßig nicht der Fall.

Darstellung von Dateien

VIM bietet auch die Möglichkeit, mehrere Dateien per Splitscreen parallel zu öffnen:

:vsp filename
cmd+w cmd+w => switch between files/viewports
cmd+w q => close active window/file

Plugins

Erst Plugins machen VIM zu einem richtig praktischen Editor, fast zu einer IDE. Anfangs hab ich es mit vim-pathogen probiert, später bin ich zu spf13 gewechselt. Ersteres baut auf Git-Submodule, zweiteres auf Vundle (und ist standardmäßig umfangreicher). Die meiner Meinung nach nützlichsten Plugins sind:

  • NERDTree (zur Darstellng eines Dateibaums)
  • Undotree
  • crtlp (Liste verwendeter Dateien)
  • Fugitive (Git-Integration)
  • Tagbar
  • Airline (eine bessere Statusleiste)
  • AutoClose
  • sessionman (Verwalten von Projekten/Sessions)

Eine passende Übersicht findet sich auf vim.wikia.com. Dort wird zum Beispiel auch netrw zur FTP-Integration genannt.

Installation von spf13:

$ curl https://j.mp/spf13-vim3 -L > spf13-vim.sh && sh spf13-vim.sh

Tagbar

Für Tagbar muss ctags auf dem System vorhanden/installiert sein. Hier eine kurze Installationsanleitung:

  1. download ctags (http://ctags.sourceforge.net/)
  2. install ctags:
$ cd path/to/ctags/
$ ./configure
$ make
$ sudo make install
$ make clean

3.  start vim & tagbar: `:TagbarOpen` or `:TagbarToggle`

Autovervollständigung

Prüfen auf Verfügbarkeit einer Autovervollständigung:

$ vim
:echo has("lua")
=> if_lua disabled -> neocomplete funktioniert nicht!
$ echo UnBundle \'neocomplete\' >> ~/.vimrc.bundles.local

Literatur

Passende und zu empfehlende (Einstiegs-)Literatur:
„vim 7 GE-PACKT“, Reinhard Wobst, mitp, ISBN 978-3-8266-1781-2

Cheatsheet

Hier ein kurzer Cheatsheet (spf13):

spf13 <leader> is: ,

<leader>e                         open NERDTree with current file
<leader>fc                        Find merge conflict markers
<leader>ff                         display all lines with keyword under cursor and ask which one to jump to
<leader>fu                        ctrl-p funky / list recently modified lines
<leader>gs                       :Gstatus         works like ‚git status‘
<leader>gd                      :Gdiff                works like ‚git diff‘
<leader>gc                      :Gcommit       works like ‚git commit‘
<leader>gl                       :Glog                 works like ‚git log‘
<leader>gp                      :Gpush            works like ‚git push‘
<leader>gr                       :Gedit              ??
<leader>gw                     :Gwrite           works like ‚git add %‘ if working-copy is active window
<leader>ge                      :Gread             works like ‚git co %‘ if working-copy is active window
<leader>ru                      list recently used files <silent> <D-r>
<leader>sc                      close session
<leader>sl                       list sessions
<leader>ss                      save session
<leader>tt                       toggle tagbar
<leader>u                        toggle undo-tree

<C-p>                                 list files

:vim /pattern/gj path
>                                          Suche in path nach pattern
>                                          Option `g`: alle Vorkommen
>                                          Option `j`: unterbinde Öffnen des ersten Suchergebnisses (der ersten Datei)
:cn                                      nächstes Suchergebnis (Datei) öffnen
:cp                                      vorheriges Suchergebnis (Datei) öffnen
:cw                                     Suchergebnisse auflisten

:!echo #                           prints current filename
:!CMD                              execute Shell-Command
:pwd                                  prints current directory

:let @/ = „“                     clear search-highlighting by clearing search-string
:noh                                  clear search-highlighting

 

WordPress auf anderen Server umziehen

In der Firma lief das interne Wiki auf einer WordPress-Installation auf einem älteren Mac. Dieser Rechner sollte ausgetauscht werden und WP musste somit umziehen (MAMP als Webserver bleibt).

    1. Datenbank komplett sichern (in diesem Fall tar-Archiv des MySQL-Verzeichnisses, da Datenbank-Server nicht starten konnte/wollte)
      1. $ cd /Applications/MAMP/db/mysql/
      2. $ tar -czpf wiki-db.tar.gz wp_wiki/
    2. Sicherung des kompletten WP-Verzeichnisses wp_wiki
      1. $ cd /Applications/MAMP/htdocs/
      2. $ tar -czpf wiki.tar.gz wp_wiki/
    3. Kopie auf neuem Server / Rechner aufspielen (tar-Archive übertragen und in jeweiligen Verzeichnissen ablegen)
      1. $ cd /Applications/MAMP/db/mysql/
      2. $ tar -xzf wiki-db.tar.gz => Verzeichnis wp_wiki wird erstellt, sollte vorher leer sein bzw. garnicht existieren
      3. $ rm wiki-db.tar.gz
      4. $ cd /Applications/MAMP/htdocs/
      5. $ tar -xzf wiki.tar.gz => Verzeichnis wp_wiki wird erstellt, sollte vorher leer sein bzw. garnicht existieren
      6. Datenbank anpassen
        1. ALTER DATABASE `wp_wiki` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
        2. UPDATE wp_option SET option_value = REPLACE(option_value, ‚http://<OLD-IP>:8888/wp_wiki‘, ‚http://<NEW-IP>:8888/wp_wiki‘) WHERE option_value LIKE ‚%http://<OLD-IP>:8888/wp_wiki%‘; /* home, siteurl, theme_mods_twentyfourteen */
        3. UPDATE wp_posts SET guid = REPLACE(guid, ‚http://<OLD-IP>:8888/wp_wiki‘, ‚http://<NEW-IP>:8888/wp_wiki‘) WHERE guid LIKE ‚http://<OLD-IP>:8888/wp_wiki%‘;
        4. UPDATE wp_posts SET post_content = REPLACE(post_content, ‚http://<OLD-IP>:8888/wp_wiki‘, ‚http://<NEW-IP>:8888/wp_wiki‘) WHERE post_content LIKE ‚%http://<OLD-IP>:8888/wp_wiki%‘;

Sollte die Anmeldung wegen des fehlenden Passwortes nicht gelingen, kann dieses natürlich auch zurückgesetzt werden. Zwar gibt es die Möglichkeit per „Passwort vergessen“-Funktion, hier dennoch die Variante per SQL:

 UPDATE wp_users SET user_pass = MD5('geheim') WHERE user_login = 'admin';

Während der nächsten (erfolgreichen) Anmeldung wird das Passwort dann durch eine stärkere Verschlüsselung ersetzt (MD5 gilt als nicht mehr sicher, die Ersetzung erfolgt durch einen WordPress-eigenen Algorithmus).

Weitere Informationen gibt es auf blogs-optimieren.de.

MAMP aktualisieren

MAMP (My Apache – MySQL – PHP) ist eine lokale Serverumgebung, nicht nur für Apples Mac-Betriebssystem OS X. Zum Aktualisieren dieser Software, bspw. um eine aktuellere PHP-Version zu nutzen, einfach zur Projekt-Website navigieren und dort das entsprechende Paket herunterladen. Zur Überprüfung der Authentizität (aus Sicherheitsgründen) kann die SHA-1-Prüfsumme verglichen werden:

$ openssl sha1 /Users/<USER>/Downloads/MAMP_MAMP_PRO_3.2.1.pkg

Die erhaltene Zeichenfolge sollte mit der auf der Website genannten übereinstimmen.

Anschließend die pkg-Datei ausführen und den Wizard durchlaufen. Eine bestehende MAMP-Installation wird im Laufe der Installation komplett übernommen, d.h. dass alle Projekte (htdocs, db) und Einstellungen in das neue MAMP-Verzeichnis kopiert werden. „Neues Verzeichnis“ deswegen, weil das MAMP-Update eigentlich eine Installation plus die Migration aller enthaltenen Projekte vom bestehenden Server enthält:

  1. Umbenennen des bestehenden MAMP-Verzeichnisses (in der Form MAMP_2015-06-16_08-26-14), sodass eine vollständige Sicherung verbleibt
  2. Installation neue MAMP-Version
  3. Migration (Kopie) des alten htdocs-Verzeichnisses

Das Update ist dann bereits durch. Nun sollte nach Ausführen der Datei /Applications/MAMP/MAMP.app der Webserver starten. Das App-Icon nun noch im Dock belassen, et voila.

Grund für das MAMP-Update bei mir war die Aktualisierung von OS X 10.7 auf 10.10 . Nach dem OS-Update startete Apache nicht mehr (MAMP 3.0.5). Die Versionshistorie von MAMP 3.2.1 deutete bereits auf dieses Verhalten hin: „Problem mit httpd.conf behoben, welches den Start von Apache verhindert hat“.

Anzumerken ist noch, dass die Konfigurations-Dateien des Apache (MAMP/conf/apache/) nicht übernommen werden. Nach Angleich der Dateien, sprich Übernahme der Aliase (Directory-Direktiven) und z.B. Aktivierung der httpd-vhosts.conf ist das alte Verhalten aber wieder hergestellt.

Verschlüsseln und Verschleiern mit PHP & MySQL

PHP und selbst MySQL bieten von sich aus genügend Möglichkeiten, um Passwörter (oder andere Zugangsdaten) zu speichern. Dabei spielt es keine Rolle, ob eine Verschlüsselung (eine Entschlüsselung ist möglich) oder lediglich eine Verschleierung (Bildung eines Hashs) stattfinden soll.

Auf webmasterpro.de oder php-einfach.de sind Beispiele zur Hash-Bildung unter PHP zu finden. Dies ist ein einfacher und zugleich relativ sicherer Weg, um Zugangsdaten per Hash zu speichern.

Per Hash abgelegte Daten können allerdings nicht wiederhergestellt werden. Um Daten zu kodieren und später wieder zu entschlüsseln, sind Verschlüsselungsalgorithmen wie AES oder (der veraltete) DES nötig. MySQL bietet AES bereits von Haus aus an, was das Verschlüsseln eigentlich recht einfach macht. Mit

SELECT AES_ENCRYPT('secret', 'my_salt');

kann bereits eine verschlüsselte Zeichenkette erzeugt werden. Mit

UPDATE table SET value_crypt = AES_ENCRYPT('secret','my_salt');

wird der verschlüsselte String secret in der Datenbank gespeichert, verstärkt durch den Salz my_salt. Dabei spielt es keine Rolle, ob das Passwort-Salz aus einem Datenbank-Datensatz oder per PHP aus einer externen Datei kommt. Wichtig ist aber, dass das Salz möglichst lang ist (bspw. 1024 Zeichen). Auslesen bzw. Entschlüsseln funktioniert mittels der Funktion AES_DECRYPT():

SELECT CAST(AES_DECRYPT(cryptedValue, 'my_salt') AS CHAR) AS decrypted;

Der CAST ist bei Verwendung im PHP-Kontext dringend nötig, wie die Praxis (nicht nur bei mir) zeigt.

Die Techniken lassen sich natürlich beliebig kombinieren. So verwende ich das Hashing per PHP-crypt() bei reinen Zugangskontrollen (Login in Anwendung XY), da dort keine Dechiffrierung nötig ist. Die Verschlüsselung per AES (MySQL) mit per PHP eingebundener Salz-Datei kommt (zusätzlich) in Systemen zum Einsatz, bei denen die Wiederherstellung der (Zugangs-)Daten auf einfache Weise benötigt wird, z.B. einer Passwortverwaltung.

Apache und PHP unter Lubuntu installieren

Apache2 als Webserver und die Skriptsprache PHP5 bilden wohl bei vielen Webworkern den Kern der Arbeit. Somit steht am Anfang die Installation dieser Software-Komponenten.

Die Installation per Terminal gestaltet sich wie folgt:

$ sudo -s
$ apt-get update
$ apt-get install apache2
$ apt-get install php5 libapache2-mod-php5

Um anschließend keine längeren Pfade anzugeben, bietet sich die Einrichtung lokaler Domains an. Dafür gibt es virtuelle Verzeichnisse im Apache. Um beispielsweise die Domain lokal.dev zu erstellen und zu konfigurieren, sind folgende Schritte nötig:

$ nano /etc/apache2/sites-available/lokal.dev.conf
$ a2ensite lokal.dev.conf
$ /etc/init.d/apache2 reload

Die Datei lokal.dev.conf kann als Kopie einer bestehenden Datei erstellt werden. Wichtig sind die Anpassung der Pfade innerhalb des virtualhost-Blocks, damit auch die richtigen Dateien erreicht werden. Mittels a2ensite wird die eben erstellte Konfiguration aktiviert (in /etc/apache2/sites-enabled wird ein Link auf eben diese Datei gesetzt). Anschließend muss die Server-Konfiguration neu eingelesen werden.

Nun muss diese „Domain“ noch auf den lokalen Lookup umgeleitet werden, damit der Browser nicht im weltweiten Netz danach sucht:

$ nano /etc/hosts
dort "127.0.0.1 localhost lokal.dev" ergänzen

Das war’s schon ..

Interessante Links dazu sind:

Using Grunt „The JavaScript Task Runner“

Grunt erleichtert definitiv die Entwicklungsarbeit. Aber wie benutze ich es?

Grunt ist abhängig von node.js bzw. dessen Paketmanager npm. Diese müssen folglich vorher installiert sein.
Im Projektverzeichnis sollte eine Datei package.json vorhanden sein:

{
  "name": "myTool",
  "description": "describe the tool"
}

Danach kann Grunt für das Projekt installiert bzw. aktiviert werden:

$ npm install grunt --save-dev

Dadurch wird neues Verzeichnis node_modules im Projektverzeichnis erstellt und Grunt kann benutzt werden. Die Option –save-dev ergänzt/erweitert die devDependencies in package.json um grunt. Analog für verwendete Grunt-Plugins gilt:

$ npm install grunt-contrib-concat --save-dev
$ npm install grunt-contrib-cssmin --save-dev
$ npm install grunt-contrib-uglify --save-dev
$ npm install grunt-contrib-watch --save-dev

Anschließend sind auch die installierten Plugins einsatzbereit, bspw.

$ grunt watch

Die Konfigurationsdatei Gruntfile.js sieht in etwa so aus:

  // Creating a wrapper
module.exports = function(grunt){
    // Initializing configuration objects
  grunt.initConfig({
      // Reading 'package.json' so that we can use setting given there
    pkg : grunt.file.readJSON('package.json'),
      // specifying the settings for css file minification
    cssmin : {
      backend : {
        expand : true,
        cwd : 'web/verwaltung/css/',
        src : [ '*.css', '!*.min.css' ],
        dest : 'web/verwaltung/css/',
        ext : '.min.css'
      },
      frontend : {
        expand : true,
        cwd : 'web/styles/',
        src : [ '*.css', '!*.min.css' ],
        dest : 'web/styles/',
        ext : '.min.css'
      },
      options: {
        banner: '/*! <%= pkg.name %> - <%= grunt.template.today("yyyy-mm-dd H:M") %> */'
      }
    },
      // specifying the settings for js file minification
    uglify : {
      backend: {
        files: {
          'web/verwaltung/js/be.min.js': ['web/js/jquery.jnotify.js','web/js/shared/dialog-service.js','web/verwaltung/js/app.js']
        }
      },
      frontendApp: {
        files: {
          'web/js/app.min.js': ['web/js/jquery.jnotify.js','web/js/shared/dialog-service.js','web/js/snv.js']
        }
      },
      frontendNgGrid: {
        files: {
          'web/js/angular-gridster.min.js': ['web/js/angular-gridster.js']
        }
      },
      options: {
        banner: '/*! <%= pkg.name %> - <%= grunt.template.today("yyyy-mm-dd H:M") %> */'
      }
    },
      // @see http://www.thegeekstuff.com/2014/10/grunt-contrib-watch-automate/
    watch: {
      backend: {
        files: ['web/verwaltung/css/*.css','web/verwaltung/js/*.js'],
        tasks: ['minifyBE'],
        options: {
          spawn:false,
          event:['all']
        }
      },
      frontend: {
        files: ['web/styles/*.css','web/js/*.js'],
        tasks: ['minifyFE'],
        options: {
          spawn:false,
          event:['all']
        }
      }
    }
  });
    // Loading grunt-packages
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
    // Registering tasks
  grunt.registerTask( 'minifyBE', [ 'cssmin:backend', 'uglify:backend' ] );
  grunt.registerTask( 'minifyFE', [ 'cssmin:frontend', 'uglify:frontendApp', 'uglify:frontendNgGrid' ] );
}

Die mit registerTask() hinterlegten Aufgaben können mittels Konsole auch separat gestartet werden:

$ grunt minifyBE

Akkordeon mit TYPO3

Akkordeons eignen sich als Design- bzw. UI-Elemente, um den Benutzer auf wichtige Inhalte zu lenken, ohne beschreibende oder weiterführende Inhalte sofort zur Verfügung zu stellen, aber diese dennoch mit wenig (Zeit-)Aufwand bereitzustellen.

Akkordeon

Zur Umsetzung als Inline-Akkordeon (im Fließtext einzubauen) müssen bspw. folgende Stellen erweitert werden:

  • Template
  • Javascript
  • CSS

Generiert wird das Akkordeon im Typoscript des Main-Templates:

lib.stdheader {
    ...
    # Vererbung von H3:
    91 < .3
    91.fontTag = <h3 class="inlineAccordionToggler">|</h3><div class="inlineAccordionElement">
    ...
}
tt_content.stdWrap {
    ...
    outerWrap = |</div>
    outerWrap.stdWrap.if.equals = 91
    outerWrap.stdWrap.if.value.field = header_layout
}

Dadurch werden Inhaltselemente vom Überschriften-Typ 91 mit einer CSS-Klasse und einem umhüllenden DIV ausgestattet. Die CSS-Klasse hinterlegen wir im eingebundenen Stylesheet:

.inlineAccordionToggler {
    color:#ff7713;
    padding:8px 0;
    margin:0;
    cursor:pointer;
    font-weight: normal;
}
.inlineAccordionActiveToggler {
    font-weight: bold !important;
}
.inlineAccordion {
    border:1px solid #e4e4e4;
    border-width:1px 0;
    margin-bottom:-1px;
    font-size:1em;
}

Momentan kann das Akkordeon aber noch nicht verwendet werden. Dafür muss der Überschriften-Typ 91 per TCEFORM definiert werden:

Web | List | Home => bearbeiten
Ressourcen-Tab => TypoScript-Konfiguration:
TCEFORM.tt_content.header_layout.addItems {
    91 = Inline-Akkordeon
}

Nun kann als Überschriften-Typ Inline-Akkordeon gewählt werden.

tceform

Jetzt fehlt lediglich noch die Javascript-Funktionalität, um das Auf- bzw. Zuklappen bereitzustellen. Bei Verwendung von jQuery könnte das dann so aussehen:

jQuery(document).ready(function(){
    ...
    init_inlineAccordion();
    ...
}
function init_inlineAccordion() {
    var togglers = $('.inlineAccordionToggler');
    if (togglers.length > 0) {
        /**
         * neues Div mit class="inlineAccord" erstellen ..
         * .. und um toggler herum in DOM einfuegen
         */
        $('.inlineAccordionToggler, .inlineAccordionElement').wrapAll('<div class="inlineAccordion"></div>');
        var allPanels = $('.inlineAccordionToggler + .inlineAccordionElement').hide();

        $('.inlineAccordion > .inlineAccordionToggler').click(function() {
            var $this = $(this),
                contentItem = $this.next();
            allPanels.slideUp();
            togglers.removeClass('inlineAccordionActiveToggler');
            if (contentItem.css('display')==='none') {
                // Inhalt einblenden:
                contentItem.slideDown();
                $this.addClass('inlineAccordionActiveToggler');
            }
              return false;
        });
    }
}

Unter Prototype hilft folgendes Snippet:

window.IBSEfehse = {
    inlineAccordion_currentItem: false,
    inlineAccordion_init: function() {
        var togglers = $$('.inlineAccordionToggler'),
            myAccordion = this;
        if (togglers.length > 0) {
            togglers.each(function(toggler,idx){
            myAccordion.inlineAccordion_hideContent(toggler);
                toggler.on('click', function(evt){
                    if (myAccordion.inlineAccordion_currentItem!==false) {
                        if (myAccordion.inlineAccordion_currentItem!==toggler) {
                            myAccordion.inlineAccordion_hideContent(myAccordion.inlineAccordion_currentItem);
                            myAccordion.inlineAccordion_showContent(toggler);
                            myAccordion.inlineAccordion_currentItem = toggler;
                        } else {
                            myAccordion.inlineAccordion_hideContent(toggler);
                            myAccordion.inlineAccordion_currentItem = false;
                        }
                    } else {
                        myAccordion.inlineAccordion_showContent(toggler);
                        myAccordion.inlineAccordion_currentItem = toggler;
                    }
                });
            });
        }
    },
    inlineAccordion_hideContent: function(link){
        var container = link.up(0);
        container.childElements().invoke('hide');
        link.show();
    },
    inlineAccordion_showContent: function(link){
        var container = link.up(0);
        container.childElements().invoke('show');
        this.inlineAccordion_currentItem = link;
    }
};
Event.observe(window, 'load', function() { IBSEfehse.inlineAccordion_init(); });

Das kann in einer bestehenden js-Datei eingearbeitet werden oder separat mittels page.includeJS im TypoScript:

page.includeJs {
    file = xyz.js
}

Formular-Spam ohne Captcha verhindern

Automatisch befüllte Formulare, Inhalte ohne (sinnvollen) Bezug zur Website oder womöglich Herabstufung durch Suchmaschinen sind ein  wahrer Graus.

Das Thema der automatisiert befüllten Formulare fand sich im Gästebuch des Volleyballvereins Grimma eV. Anfangs einmal am Tag (meist gegen 22:00 fünf gleichzeitige Einträge), später in unregelmäßigen Abständen mehrere Einträge täglich. Das genannte Formular sollte allerdings weiterhin ohne Captcha auskommen.

Irgendwo hatte ich gelesen, dass Webcrawler bei solchen Aktionen ALLE Eingabefelder befüllen, wodurch das Erkennen eines automatisierten Befüllens einfach umzusetzen wäre.

Somit zur Umsetzung: Das Formular wird um ein weiteres Input-Element ergänzt, z.B.:

<form action="" method="post>
  <!-- bisherige Eingabefelder -->
  <input type="text" name="myCaptcha" value="" style="display:none;">
</form>

Beim Absenden des Formulars könnte dies nun bereits Clientseitig per Javascript geprüft werden, was bei Webcrawlern allerdings wenig Aussicht auf Erfolg hätte, da JS nicht zur Ausführung käme. Somit muss die Prüfung Serverseitig erfolgen, z.B.:

<?php
# on form submit per POST:
if (!empty($_POST['myCaptcha'])) {
  # prevent positive actions
  $error = true;
  # don't forget input-validation!
  # send email to webmaster here ..
  mail('webmaster@mySi.te','SPAM','possible spam on form: ' . $_POST['myCaptcha']);
  # .. or do whatever you want
}
?>

Es wird lediglich geprüft, ob myCaptcha einen Wert besitzt. Wenn ja, wird im obigen Beispiel eine eMail zur Kenntnisname versendet. Funktioniert recht zuverlässig.