Alle Beiträge von adlerweb

Der Hacker und die sieben Admins

Es war einmal eine Gruppe von Admins, welche sich morgens um 11 nach langer Nacht zum Ruhen ins Büro begaben. Sie setzten sich und sahen gleich, dass jemand da gewesen war. Der Erste fing an zu fragen:

„Wer hat auf meinem Program Files geschrieben?“

Der Zweite fragte:

„Wer hat von meiner password.txt gelesen?“

Der Dritte fragte.

„Wer hat mein CD-Laufwerk geöffnet?“

Der Vierte:

„Wer hat meinen Desktophintergrund geändert?“

Der Fünfte:

„Wer hat meinen Admin-Konto genutzt?“

Der Sechste:

„Wer hat meinen Postausgang gefüllt?“

und der Siebente fragte:

„Wer hat das Trollface gedruckt?“

Wie die Admins also gefragt hatten, sahen sie sich nach ihren Gehaltszahlungen um, und fragten:

„Wer hat sich auf unseren Banken angemeldet?“

bis auf den Siebenten, der fragte nicht so, sondern:

„Wo ist mein Geld?“,

denn da bediente sich der Hacker.

So, neuer Kernel fertig – weiter im Programm 😉

Atom: Deutsches Tastaturlayout nutzbar machen

Atom ist ein freier, modularer Texteditor aus dem Dunstkreis von GitHub, welcher trotz vieler Funktionen einen akzeptablen Ressourcenhunger (naja, sofern das Chrome-basiert möglich ist) vorlegt. Leider ist dessen Nutzung mit Tastaturen außerhalb des QWERTY-Raumes wegen eines Bugs seit über einem Jahr nahezu unmöglich. So ist es z.B. unter Windows unmöglich mit deutschem Tastaturlayout einen Backslash (\) oder das @-Zeichen zu tippen.

Nunja, mit einigen Tricks lässt sich das Problem zumindest temporär umgehen: Für den ersten Schritt klickt man im Editor auf File->Open Your Keymap

Bild: https://adlerweb.info/blog/wp-content/uploads/2015/07/keymap-239×300.png

Hier lassen sich die meisten Tastenkombinationen konfigurieren, u.A. auch die Aktion, welche das @ blockiert entfernen:

'.platform-win32 atom-text-editor':
  'ctrl-alt-[': 'unset!'
  'ctrl-alt-q': 'unset!'

Obwohl hiermit eigentlich auch das \ konfiguriert werden sollte, funktionierte der Workarround für diese Taste bei mir nicht. Hier musst ich zusätzlich das Addon „keyboard-localization“ installieren.

Welp. Immerhin nutzbar.

Parkfest Saffig 2015

Am 27. und 28. Juni 2015 fand das traditionelle Saffiger Parkfest satt. Dieses Jahr lag die Ausrichtung in den Händen der Freiwilligen Feuerwehr Saffig, deren Förderverein sein 10-jähriges Bestehen feiern durfte. Samstags startete die Feierlichkeit mit einer Schauübung, bei welcher die Wehren der Pellenz zusammen mit der Drehleiter Andernach sowie dem DRK Saffig auf dem Gelände der Barmherzigen Brüder Saffig ihr Können vor den Augen der zahlreichen Besucher unter Beweis stellten. Hierbei durften Interessierte die Ausrüstung aus der Nähe betrachten und den Fachleuten ihre Fragen vortragen.

https://www.youtube.com/watch?v=QW0dQUZpRzg

Im Anschluss ging es dann im „Schlosspark“ bei sonnigem Wetter weiter: Die Feuerwehren Nickenich und Saffig erhielten zwei neue Mannschaftstransportfahrzeuge (MTF) sowie einen Anhänger, welche durch die Pastöre der evangelischen und katholischen Kirche zuvor eingesegnet wurden. Im Anschluss konnten die Besucher im „schönsten Biergarten der Pellenz“ bis tief in die Nacht feiern.

https://www.youtube.com/watch?v=jGDjVsGBu3w

Linux: Partitionen innerhalb einer Image-Datei/LV mounten

Bisher speicherte ein Windows-System seine Datensicherung per CIFS (aka Samba) auf einer Linux-basierten NAS. Das Konzept ist dabei schon etwas älter – die Windows-Software unterstützte bei der Einrichtung weder Kompression noch Deduplizierung, sodass hier das ZFS-Dateisystem der Linux-Kiste einiges an Platz einsparen konnte. Inzwischen ist die Windows-Software gewachsen: Kompression und Deduplizierung werden bereits vorab durchgeführt, dafür machten die vielen neuen Funktionen immer häufiger Probleme mit CIFS. Kaputte Locks, schlechte Performance, Verbindungsabbrüche – alles lässt sich recht genau auf die Kombination aus dem Server und CIFS zurückführen. Während eine „normale“ Kopie unter Linux mit etwa 100MB/s von statten geht schafft der Server nur 4MB/s. Andere Protokolle sind schneller, andere Windows-Server haben ebenfalls keine Probleme. Inzwischen sind ohnehin neue Platten fällig – Zeit hier gegenzusteuern.

Die Speicherung soll weiterhin auf der Linux-Kiste erfolgen – dort ist die Sicherung und Verwaltung deutlich zuverlässiger und flexibler. Andererseits habe ich keine Lust auf CIFS-Fehlersuche, also lassen wir es einfach weg – die Lösung: iSCSI. Der Windows-Server greift direkt auf den rohen Datenträger des Linux-Systems zu. Da wie erwähnt keine Linux-seitige Kompression stattfinden muss wird diese einfach per LVM verwaltet und ist über den Kernel-iSCSI-Server exportiert. Das Anlegen verläuft ohne Probleme, müssen nur noch die Daten rüben. Mit 4MB/s. Bei fast 4TB. Mit 5 Tagen Restzeit. Nope.

Also, fassen wir nochmal zusammen: Sowohl der alte als auch der neue Datenbestand liegen auf dem selben Linux-System. Warum nicht einfach da kopieren? NTFS-Schreiben unter Linux ist zwar nicht schnell, aber die 4MB/s-Marke sollte zu schaffen sein. Problem: Auf dem LVM-LV hat Windows automatisch eine GPT-Partitionstabelle angelegt, sie lässt sich also nicht direkt unter Linux mounten.

Um trotzdem an das NTFS-System zu kommen müssen wir erst mal wissen wo die passende Partiton beginnt. Hier kommt parted zum Einsatz, welcher auch GPT-Partitionstabellen unterstützt. Da wir die Angaben in Byte, nicht den üblichen Sektoren, benötigen muss die Anzeige erst ungeschaltet werden.

[root@nas ~]# parted /dev/speicher2/windows9
GNU Parted 3.2
/dev/dm-1 wird verwendet
Willkommen zu GNU Parted! Rufen Sie "help" auf, um eine Liste der verfügbaren Befehle zu erhalten.
(parted) unit B
(parted) print
Modell: Linux device-mapper (linear) (dm)
Festplatte  /dev/dm-1:  4178147540992B
Sektorgröße (logisch/physisch): 512B/512B
Partitionstabelle: gpt
Disk-Flags:

Nummer  Anfang      Ende            Größe           Dateisystem  Name                          Flags
 1      17408B      134235135B      134217728B                   Microsoft reserved partition  msftres
 2      135266304B  4178146492415B  4178011226112B  ntfs         Basic data partition          msftdata
(parted) quit

Wie wir sehen wurden auf dem LVM-LV durch Windows zwei Partitionen angelegt: Die Erste enthält Managementdaten, die Zweite ist unsere eigentliche NTFS-Datenpartition. Letztere beginnt bei 135266304 Byte – genau diesen Bereich müssen wir auf dem Datenträger also überspringen um auf das NTFS-Dateisystem zugreifen zu können. Also legen wir uns ein passendes Loop-Device an. Da ich bereits ein anderes Device aktiv habe nutze ich loop2 – generell ist die Zahl wählbar.

[root@nas ~]# losetup -o 135266304 /dev/loop2 /dev/speicher2/windows9
[root@nas ~]# mount -t ntfs-3g /dev/loop2 /mnt/test/
[root@nas ~]# mount | grep test
/dev/loop2 on /mnt/test type fuseblk (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other,blksize=4096)
[root@nas ~]# df -h test/
Dateisystem    Größe Benutzt Verf. Verw% Eingehängt auf
/dev/loop2      3,8T    604G  3,3T   16% /mnt/test

Passt doch, schon haben wir unter Linux nativen Zugriff auf das Device. Das Kopieren läuft nun mit 90MB/s – nicht ganz die bestmögliche Geschwindigkeit, für NTFS über fuse aber kein Schlechter wert – und etwas schneller als die 4MB/s der Windows-Kiste.

Linux als iSCSI-Target

iSCSI ermöglicht es entfernte Festplatten über Ethernet an einen Rechner anzubinden. Hierbei kümmert sich nicht wie bei CIFS/NFS/… der Zielrechner um die Dateiverwaltung, sondern der Client behandelt die Festplatte wie eine lokal eingebaute – incl. Partitionierung, Dateisystem & Co. Ethernet ist hierbei zwar nicht die schnellste Lösung, jedoch erheblich günstiger und flexibler als professionelle Lösungen wie Fibre-Channel.

Ich nutze ein System auf Basis von Arch Linux, welches eine Partition an einen anderern Rechner bereitstellen soll. Im iSCSI-Jargon nennt sich dies „Target“, der Client „Initiator“. Zur Verwaltung unter Arch nutze ich die Free-Branch des LIO targetcli, dies ist im AUR zu finden.

Im ersten Schritt starten wir das iSCSI-System. Hierbei werden die nötigen Module geladen und ggf. zuvor definierte Geräte exportiert.

systemctl start target

Nun starten wir das Management-Tool. Es stellt eine eigene Konsole zur Verfügung. Erst wird die Festplatte mit einem internen Namen verknüpft. Ich nutze hier zur besseren Übersicht den Partitionsnamen

targetcli
cd backstores/block
create sda3 /dev/sda3

Im Anschluss erstellen wir eine „Target Portal Group“ (=Konfigurationsspeicher für das iSCSI-Target) samt passendem „IQN“ (=iSCSI-Adresse). Letztere wird im nächsten Schritt benötigt, mit ls kann man bei allen Schritten die aktuelle Konfiguration anzeigen lassen.

cd /iscsi
create
ls
o- iscsi ........................................ [Targets: 1]
  o- iqn.[…] ....................................... [TPGs: 1]

nun registrieren wir die zuvor bekanntgemachte Festplatte als LUN (=logischer Speicher)

cd iqn.****/tpg1/luns
create /backstores/block/sda3

Nun erstellen wir das Portal selbst (=legen einen Server an), erlauben die Adresse des Initiators, welche man am Client nachschlagen kann, und legen Benutzername und Kennwort fest

cd ../../portals
create
cd ../../acls
create iqn.YOUR.INITI:ATOR
cd iqn.YOUR.INITI:ATOR
set auth userid=foo
set auth password=bar

Zuletzt wird die Konfiguration gespeichert. Das iSCSI-Target ist nun nutzbar.

cd /
saveconfige

Sinnvollerweise sollta man target noch mit einem „systemctl enable target“ beim Systemstart automatisch starten lassen.

BTRFS-Recovery

Narf… Das war zu viel. Durch einen Umbau hatte sich offenbar ein Kabel im Lüfter meines Chipsatzes verheddert – keine gute Voraussetzung, wenn man mal eben Wine neu kompiliert. Es kommt, was kommen musste: Kernel panic.

Nunja, Fehler gefunden, weiter gehts. Nicht. Zwar startete das System, meine Home-Partition blieb aber verschwunden. Kurzer Blick auf den Aufbau: Mein Home liegt auf einer separaten Partition, ist mit LUKS verschlüsselt und nutzt – da ich die gigabyteweise IRC-Logs vorratsdatenspeichere und transparente Komprimierung wollte – BTRFS. Das Log verrät: LUKS selbst läuft, mount hingegen scheint sich aufzuhängen. In dmesg sieht alles OK aus, nur die normalen BTRFS-Meldungen, keine Fehler.

Strange, aber Reboot fixed ja fast alles, also schnell die magischen Zeichen eingetippt und zur Sicherheit im Rescue-Mode gestartet. Dort per Hand cryptsetup aufgerufen und erst mal btrfsck losgelassen. Kein Fehler. Mount? Nope.

OK, nun bin ich genervt, vor allem da die Kiste nicht unbedingt schnell bootet. Qemu to the rescue, also schnell ein Debian geklont und die Partition vorgesetzt. Schlechte Idee. Die bei Debian genutzten BTRFS-Libs/Module sind eine Nummer zu alt und können mit der Partition gar nichts anfangen. Ein Arch-Download später kann es weiter gehen.

Erst einmal ein normaler mount in der VM – vielleicht ist ja nur der Host kaputt, außerdem ist auf der CD ein älterer Kernel. Leider ohne erfolg, also VM-Reboot und den Hammer ausgepackt. Mit „mount -o recovery,nospace_cache“ lassen sich viele Fehler schon beheben, aber auch hier scheint der mount nichts zu vollziehen. Keine Reaktion, keine IO-Last, keine Fehlermeldung.

Die Lösung: btrfs_zero_log. Hiermit werden die offenen Transaktionen gelöscht. Die kann zwar zu Datenverlust von Änderungen, die kurz vor Absturz geschrieben wurden, führen, aber besser als gar keine Partition. Der mount in der VM war schlussendlich erfolgreich und auch der Host verfügte kurze Zeit später wieder über alle Dateien. Warum btrfsck das offenbar defekte Log nicht ankreidete ist mir allerdings ein Rätsel – sollte das nicht die Aufgabe des Tools sein?

Software als Windows-Service

Programme starten ist toll. Einige müssen aber immer wieder gestartet werden, hierzu nutzt man unter Windows so genannte „Dienste“. Leider sind das unter Windows NT (also auch z.B. Windows 7/8/10, Windows Server 2008/2012) keine einfachen Programme, sondern erfordern spezielle Registrierungen. Leider gent so etwas (soweit mir bekannt) nicht mit Hausmitteln.

Abhilfe schafft ein kleines Tool: winserv verwandelt ein beliebiges Programm in einen passenden Dienst. Hierbei gibt es folgende Modi:

install richtet ein Programm als Dienst ein
configure ändert einen vorhandenen Dienst
uninstall entfernt den Dienst

Zum Anlegen gibt man folgende Parameter:

-displayname Angezeigter Name des Dienstes
-description Beschreibung (das, was in der Diensteverwaltung in der zweiten Spalte steht)
-start Start-Art des Dienstes – „auto“ „demand“ oder „disabled“ (Bedeutung selbstredend, oder?)

Beispiel:

winserv install testdienst -displayname "Test-Dienst" -description "Test 123" -start auto c:\BLA.EXE

Alternativ kann man auch einen Geplanten Task mit dem Trigger „Bei Systemstart“ verwenden, hier muss man jedoch auf die komfortableren Prüf- und Verwaltungsfunktionen des Dienst-Managers verzichten.

Q’n’D Windows-Shutdown-Daemon in PHP

Alles in Deckung, ich mache wieder unschönes Zeugs. Ich hatte ja bereits erklärt wie man über RPC/Samba einen Windows-PC von Linux herunterfahren kann. Leider ist die Installation von Samba aus Embedded-Systemen eine eher ungünstige Sache und ist in meinem Fall wegen Speichermangel nicht drin. Allerdings ist sowohl auf dem Embedded-System als auch dem betroffenen Rechner PHP verfügbar. I aim to misbehave.

Auf dem herunterzufahrenden Windows-Rechner wird per PHP ein TCP-Socket geöffnet. Dieser Code basiert auf einem Beispiel von Michael Kliewe. Um Scriptkiddies nicht direkt Zugang zu geben ist etwas (sehr schlechte) Challenge-Response-Authentifizierung drin – nunja, ist kein kritisches System. Wird der Client bestätigt folgt ein simples Shutdown per exec. Nicht viel, aber schnell Fertig und funktioniert:

<?php
class SocketChatServer {
    private $address = '0.0.0.0';   // 0.0.0.0 means all available interfaces
    private $port = 12345;          // the TCP port that should be used
    private $maxClients = 10;
 
    private $clients;
    private $socket;
    
    private $salt = 'YourSalt';
    private $pass = 'YourSecret';
 
    public function __construct() {
        // Set time limit to indefinite execution
        set_time_limit(0);
        error_reporting(E_ALL ^ E_NOTICE);
    }
 
    public function start() {
        // Create a TCP Stream socket
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        // Bind the socket to an address/port
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($this->socket, $this->address, $this->port);
        // Start listening for connections
        socket_listen($this->socket, $this->maxClients);
 
 
        $this->clients = array('0' => array('socket' => $this->socket));
 
        while (true) {
            // Setup clients listen socket for reading
            $read[0] = $this->socket;
            for($i=1; $i<count ($this->clients)+1; ++$i) {
                if($this->clients[$i] != NULL) {
                    $read[$i+1] = $this->clients[$i]['socket'];
                }
            }
 
            // Set up a blocking call to socket_select()
            $ready = socket_select($read, $write = NULL, $except = NULL, $tv_sec = NULL);
 
            /* if a new connection is being made add it to the client array */
            if(in_array($this->socket, $read)) {
                for($i=1; $i < $this->maxClients+1; ++$i) {
                    if(!isset($this->clients[$i])) {
                        $this->clients[$i]['socket'] = socket_accept($this->socket);
                        socket_getpeername($this->clients[$i]['socket'], $ip);
                        $this->clients[$i]['ipaddy'] = $ip;
 
                        socket_write($this->clients[$i]['socket'], 'Welcome to the Shutdown Control System'."\r\n");
                        socket_write($this->clients[$i]['socket'], 'Your personal code is '.md5($this->salt.strftime('%Y-%m-%d').$ip)."\r\n");
 
                        $this->log("New client #$i connected: " . $this->clients[$i]['ipaddy']);
                        break;
                    } elseif($i == $this->maxClients - 1) {
                        $this->log('Too many Clients connected!');
                    }
 
                    if($ready < 1) {
                        continue;
                    }
                }
            }
 
            // If a client is trying to write - handle it now
            for($i=1; $i<$this->maxClients+1; ++$i) {
                if(in_array($this->clients[$i]['socket'], $read)) {
                    $data = @socket_read($this->clients[$i]['socket'], 1024, PHP_NORMAL_READ);
 
                    if($data === FALSE) {
                        unset($this->clients[$i]);
                        $this->log('Client disconnected!');
                        continue;
                    }
 
                    $data = trim($data);
 
                    if(!empty($data)) {
                        switch ($data) {
                            case 'exit':
                            case 'quit':
                                socket_write($this->clients[$i]['socket'], "Disconnecting without any action, Goodbye.\r\n");
                                $this->log("Client #$i is exiting");
                                unset($this->clients[$i]);
                                continue;
                            case 'shutdown-'.md5($this->pass.md5($this->salt.strftime('%Y-%m-%d').$ip)):
                                // first write a message to all connected clients
                                for($j=1; $j < $this->maxClients+1; ++$j) {
                                    if(isset($this->clients[$j]['socket'])) {
                                        if($this->clients[$j]['socket'] != $this->socket) {
                                            socket_write($this->clients[$j]['socket'], "System will be shut down now...\r\n");
                                        }
                                    }
                                }
                                // Close the master sockets, server termination requested
                                exec('shutdown /s /t 15 /c "System wurde per Fernbedienung ausgeschaltet"');
                                $this->log("Terminated server (requested by client #$i)");
                                exit;
                            case 'whoami':
                                // first write a message to all connected clients
                                socket_write($this->clients[$i]['socket'], "You are registred with the source IP ".$this->clients[$i]['ipaddy']." - your Code was ".md5($this->salt.strftime('%Y-%m-%d').$ip)." \r\n");
                                
                                break(2);
                            default:
                                for($j=1; $j < $this->maxClients+1; ++$j) {
                                    if(isset($this->clients[$j]['socket'])) {
                                        if(($this->clients[$j]['socket'] != $this->socket)) {
                                            $this->log($this->clients[$i]['ipaddy'] . ' is sending a message to ' . $this->clients[$j]['ipaddy'] . '!');
                                            socket_write($this->clients[$j]['socket'], '[' . $this->clients[$i]['ipaddy'] . '] says: ' . $data . "\r\n");
                                        }
                                    }
                                }
                                break(2);
                        }
                    }
                }
            }
        } // end while
    }
 
    private function log($msg) {
        // instead of echoing to console we could write this to a database or a textfile
        echo "[".date('Y-m-d H:i:s')."] " . $msg . "\r\n";
    }
}

$srv = new SocketChatServer;
$srv->start();
?>

Der Client bastelt sich eine passende Authentifizierung und spielt Zündknopf:

function pc_shutdown() {                                                                                                                                                                                                              
                $pass = 'YoutSecret';                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
                $fp = fsockopen('pc.name', 12345, $errno, $errstr, 2);                                                                                                                                                                              
                if(!$fp) return false;                                                                                                                                                                                                        
                                                                                                                                                                                                                                              
                $line = fgets($fp); //Greeting                                                                                                                                                                                                
                $line = fgets($fp); //Hash                                                                                                                                                                                                    
                                                                                                                                                                                                                                              
                preg_match('/code is ([\w\d]{32})/', $line, $match);                                                                                                                                                                          
                $seed = $match[1];                                                                                                                                                                                                            
                                                                                                                                                                                                                                              
                $cmd = 'shutdown-'.md5($pass.$seed)."\n";                                                                                                                                                                                     
                                                                                                                                                                                                                                              
                fwrite($fp, $cmd);                                                                                                                                                                                                            
                                                                                                                                                                                                                                              
                var_dump(fgets($fp));                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
                fclose($fp);                                                                                                                                                                                                                  
        }

Hoffen wir, dass es läuft und ich nie wieder drauf schauen muss, denn irgendwo tut sowas selbst mir weh 😉

Symantec Backup Exec: Bandstatus per PowerShell auslesen

Symantec Backup Exec ist eine in Windows-Umgebungen recht verbreitete Backup-Software. Seit einigen Versionen lassen sich große Teile auch über die „Microsoft-Bash“ PowerShell steuern. Für meinen Zweck wollte ich eine Liste aller im angeschlossenen Bandwechseler eingelegten Bänder und deren Status, so kann ich feststellen welche entnommen werden sollen. Im ersten Schritt muss das PowerShell-Modul geladen werden:

Import-Module 'C:\Program Files\Symantec\Backup Exec\Modules\BEMCLI\BEMCLI'

Nun wird die Liste der Bännder im Wechsler ausgelesen – hier lässt sich feststellen welches Band in welchem Slot des Wechslers verfügbar ist:

Get-BERoboticLibrarySlot -RoboticLibraryDevice "TapeLib01"

Name       SlotNumber IsCleaningSlot  Media
----       ---------- --------------  -----
Schacht 1  1          False           ADLR03L5
Schacht 2  2          False           ADLR05L5
Schacht 3  3          False           ADLR11L5
Schacht 4  4          False           ADLR13L5
Schacht 5  5          False           ADLR24L5
Schacht 6  6          False           ADLR31L5
Schacht 7  7          False           ADLR33L5
Schacht 8  8          False           ADLR48L5
Schacht 9  9          False           ADLR34L5
Schacht 10 10         False           ADLR41L5
Schacht 11 11         False           ADLR49L5
Schacht 12 12         False           ADLR60L5

Zuletzt werden die zugehörigen Banddaten wie z.B. die Dauer des Software-Schreibschutzes gelesen:

Get-BEMedia -MediaVault "Online-Bandmedien" -Verbose | Format-List Name,MediaSet,OverwriteProtectedUntilDate,OverwriteProtectedUntilDate -Force

Name                        : ADLR03L5
MediaSet                    : Monatssicherungen (12 Monate schreibschutz)
OverwriteProtectedUntilDate : 06.03.2016 02:39:53
[…]

Die Daten werden in meinem Fall am Ende einer externen Schnittstelle übergeben, welche die Bandstati sortiert und entsprechende Warnmeldungen zur Entnahme generieren kann.

FTP-Uploads per Shell-Script

Ab und an komme auch ich nicht drumrum mit Zielen zu arbeiten, welche keine SSH/SFTP-Verbindung zulassen, sondern nur per FTP(S) erreichbar sind. Grade beim Automatisieren über Shell-Scripte ist das reichlich unschön. Ein praktischer Client ist in diesem Fall das Urgestein ncftp. Mit dessen Tools lässt sich z.B. schnell ein ein entferntes Verzeichnis löschen und durch die lokale Kopie ersetzen:

echo 'rm foo/bar/*
rm foo/baz/*
rmdir foo/bar
rmdir foo/baz
' | ncftp -u $USER -p $PASS $HOST

ncftpput -R -u $USER -p $PASS $HOST /foo/* ~/dev/foo/*