С чего началась эта история: однажды перестала работать синхронизация между двумя серверами. На одном из серверов (под управлением Windows) в расшаренной папке лежали документы, а на втором (под управлением Debian) был поднят апач с webdav. В папке на первом сервере было несколько подпапок. В одной лежали документы, а в остальных были сделаны ярлыки на документы, таким образом документы были рассортированы по подпапкам. И содержимое папки на первом сервере синхронизировалось с папкой на втором сервере следующим образом: копировалось содержимое папки, а затем ярлыки заменялись на файлы, на которые они указывали. То есть, если ярлык, к примеру, указывал на документ corporate-template-65178.doc, то ярлык удалялся, а на его место помещался этот самый corporate-template-65178.doc
Эта система работала три года, а потом внезапно работать перестала, безо всяких предупреждений.
И её надо было во что бы то ни стало починить.
Если еще интересно, вэлкам под кат.
Итак. Был проведен разбор полетов, опрошено большое количество людей, перекопано море документации, но нигде не было найдено никакой информации о том, кто это делал и когда. Когда началось перекапывание серверов, был найден сервер, который являлся почтовым шлюзом. И именно на нем были обнаружены скрипты на vbs и cmd, которые и занимались синхронизацией. Сначала из расшаренной папки копировалось rsync'ом содержимое в локальную, потом ярлыки заменялись на документы посредством vbs-скрипта, а затем через cmd с использованием scp для Windows (без пароля, с ключом) файлы копировались в webdav, и в процессе копирования терялось время последнего изменения.
А перестала работать эта система потому, что изменились ключи sshd, соответственно, файлы перестали копироваться в webdav.
Администраторы почтового шлюза были очень удивлены тем, что на их сервере, где не должно быть больше никакого функционала, кроме отправки почты, были обнаружены столь странные скрипты и задание в планировщике, осуществляющее указанные действия. Вместе с этим они негативно и без цензуры высказались на тему использования этого сервера в качестве перевалочной базы.
Схема действительно была организована странно.
С первого сервера на второй данные копировал третий, который вообще никакого отношения к первым двум не имел.
Практически сразу же в голову пришла мысль избавиться от посредника, тем самым упростив схему работы системы.
Поскольку серверов два, скрипты синхронизации надо было делать на каком-то из них.
Сервер на Windows использовать оказалось невозможно в связи с тем, что административных прав на него не было, и единственной задачей, которую он выполнял, было функционирование как файловый сервер.
Таким образом, остался только один вариант — реализовать подобный функционал на linux'е.
Задача сразу же осложнилась тем, что в свободном доступе не было средств для парсинга проприетарного формата виндовых ярлыков (формата .lnk). С другой стороны, это придало задаче интереса.
Задачу решать надо было срочно, и к помощи был призван гугл, в котором нашлась аж целая одна программка, и та уже в бинарном виде. Поскольку неизвестно, что там внутри, решено было не использовать эту программу, а написать свою.
В том же гугле был найден документ, полученный реверс-инжинирингом, который описывает структуру файлов этого формата.
Он и был использован как источник информации.
Сама система включила в себя следующие части:
1) Программа на языке С, которая парсит ярлыки
2) скрипты синхронизации и обработки
3) задание в кроне
Итак,
1) Программа
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
struct TLinkHeaderInfo{
//4 bytes Always 4C 00 00 00 This is how windows knows it is a shortcut file
unsigned long Signature;
//16 bytes GUID for shortcut files The current GUID for shortcuts.
//It may change in the future. 01 14 02 00 00 00 00 00 C0 00 00 00 00 00 46
unsigned char GUID[16];
//4 bytes Shortcut flags Shortcut flags are explained below
unsigned long ShortcutFlags;
//4 bytes Target file flags Flags are explained below
unsigned long TargetFileFlags;
//8 bytes Creation time
unsigned char CreationTime[8];
//8 bytes Last Access time
unsigned char LastAccessTime[8];
//8 bytes Modification time
unsigned char ModificationTime[8];
//4 bytes File length
//The length of the target file. 0 if the target is not a file.
//This value is used to find the target when the link is broken.
unsigned long FileLength;
//4 bytes Icon number
//If the file has a custom icon (set by the flags bit 6),
//then this long integer indicates the index of the icon to use. Otherwise it is zero.
unsigned long IconNumber;
//4 bytes Show Window
//the ShowWnd value to pass to the target application when starting it.
//1:Normal Window 2:Minimized 3:Maximized
unsigned long ShowWindow;
// 4 bytes Hot Key The hot key assigned for this shortcut
unsigned long HotKey;
// 4 bytes Reserved Always 0
unsigned long Reserved1;
// 4 bytes Reserved Always 0
unsigned long Reserved2;
};
void main(int argc, char** argv)
{
const int HEADER_SIZE=76;
const int FILE_ALLOCATION_INFO_HEADER_SIZE=28;
char TempString[255];
int i, LinkFileSize;
char LinkFileName[255];
char ExtractedFileName[255];
FILE* LinkFile;
struct stat FileStatInfo;
struct TLinkHeaderInfo HeaderStructure;
size_t result;
long TempInt=0;
char LinkFileContent[10240];
if(strcmp(argv[1],"")==0)
{
printf("No parameter used.n");
return;
};
printf("FileName: %sn",argv[1]);
LinkFile=fopen(argv[1],"r");
if(LinkFile==NULL)
{
fclose(LinkFile);
printf("No file found.n");
return;
};
// Open LNK File and parse it for file name
// Get it size
stat(argv[1],&FileStatInfo);
printf("Link Filesize: %dn", FileStatInfo.st_size);
LinkFileSize=FileStatInfo.st_size;
// read file to array
result = fread (LinkFileContent,1,LinkFileSize,LinkFile);
if (result != LinkFileSize)
{
printf ("Reading errorn");
};
fclose(LinkFile);
HeaderStructure.Signature=LinkFileContent[0]*65536*256;
HeaderStructure.Signature+=LinkFileContent[1]*65536;
HeaderStructure.Signature+=LinkFileContent[2]*256+LinkFileContent[3];
printf("Signature: %Xn",HeaderStructure.Signature);
printf("GUID: ");
for(i=0;i<16;i++)
{
HeaderStructure.GUID[i]=LinkFileContent[4+i];
printf(" %X",HeaderStructure.GUID[i]);
};
printf("n");
HeaderStructure.ShortcutFlags=LinkFileContent[23]*65536*256;
HeaderStructure.ShortcutFlags+=LinkFileContent[22]*65536;
HeaderStructure.ShortcutFlags+=LinkFileContent[21]*256+LinkFileContent[20];
printf("Shortcut Flags: %Xn",HeaderStructure.ShortcutFlags);
HeaderStructure.TargetFileFlags=LinkFileContent[27]*65536*256;
HeaderStructure.TargetFileFlags+=LinkFileContent[26]*65536;
HeaderStructure.TargetFileFlags+=LinkFileContent[25]*256+LinkFileContent[24];
printf("Target File Flags: %Xn",HeaderStructure.TargetFileFlags);
for(i=0;i<8;i++){HeaderStructure.CreationTime[i]=LinkFileContent[28+i];};
for(i=0;i<8;i++){HeaderStructure.LastAccessTime[i]=LinkFileContent[36+i];};
for(i=0;i<8;i++){HeaderStructure.LastAccessTime[i]=LinkFileContent[44+i];};
HeaderStructure.FileLength=LinkFileContent[55]*65536*256;
HeaderStructure.FileLength+=LinkFileContent[54]*65536;
HeaderStructure.FileLength+=LinkFileContent[53]*256+LinkFileContent[52];
printf("Target File Size (bytes): %dn",HeaderStructure.FileLength);
HeaderStructure.IconNumber=LinkFileContent[59]*65536*256;
HeaderStructure.IconNumber+=LinkFileContent[58]*65536;
HeaderStructure.IconNumber+=LinkFileContent[57]*256+LinkFileContent[56];
printf("IconNumber: %Xn",HeaderStructure.IconNumber);
HeaderStructure.ShowWindow=LinkFileContent[63]*65536*256;
HeaderStructure.ShowWindow+=LinkFileContent[62]*65536;
HeaderStructure.ShowWindow+=LinkFileContent[61]*256+LinkFileContent[60];
printf("ShowWindow Attributes: %Xn",HeaderStructure.IconNumber);
HeaderStructure.HotKey=LinkFileContent[67]*65536*256;
HeaderStructure.HotKey+=LinkFileContent[66]*65536;
HeaderStructure.HotKey+=LinkFileContent[65]*256+LinkFileContent[64];
printf("HotKey Attributes: %Xn",HeaderStructure.HotKey);
// Now extract file name and path
unsigned int OFFSET=76+(unsigned char)LinkFileContent[76];
OFFSET+=(unsigned char)LinkFileContent[77]*256+(unsigned char)LinkFileContent[78]+2;
OFFSET+=LinkFileContent[OFFSET];
printf("Share name: ");
printf("%sn",&LinkFileContent[OFFSET]);
OFFSET+=strlen(&LinkFileContent[OFFSET]);
OFFSET++;
printf("File name: ");
printf("%sn",&LinkFileContent[OFFSET]);
}
Ничего изощренного в программе нет.
Компилируем ее
gcc ./lnkinfo.c -o lnkinfo
Выкидываем отладочную информацию
strip ./lnkinfo
В исходник не включен разбор флагов типа местонахождения файла (сетевая папка, съемный диск и т.д.), при желании можно дополнить исходник, внимательно прочитав документ, ссылка на который дана выше.
Самый главный функционал готов, теперь надо его применить.
2) Скрипты.
Первый скрипт осуществляет основные операции
Находится он, скажем, в папке /usr/doc-sync
#!/bin/bash
DOMAIN=MYDOMAIN
USER=MYUSER
PASSWORD=MYPASSWORD
mount.cifs "//server/doc" /mnt/doc -o ro,dom=$DOMAIN,user=$USER,pass=$PASSWORD
cd /usr/doc-sync
WHERE_TO_PUT=/usr/local/webdav/files/docs
rm -rf $WHERE_TO_PUT/*
rsync -avz /mnt/doc/DocFolder1 $WHERE_TO_PUT
rsync -avz /mnt/doc/DocFolder2 $WHERE_TO_PUT
rsync -avz /mnt/doc/DocFolder3 $WHERE_TO_PUT
umount /mnt/doc
# Находим все файлы с расширением .lnk, прогоняем их программой lnkinfo и выдергиваем из вывода
# название самого файла ярлыка и его таргета (файла, на который он указывает)
find $WHERE_TO_PUT -name "*.lnk" | sed -e 's/ /@/g' | awk '{printf "./lnkinfo "$1" | grep ile | grep ame:n"}'
| sed -e 's/@/ /g'
| sed -e 's/(/\(/g'
| sed -e 's/)/\)/g'
| sed -e 's/ /\ /g'
| sed -e 's/lnkinfo\ /lnkinfo /g'
| sed -e 's/\ grep/ grep/g'
| sed -e 's/grep\/grep/g'
| sed -e 's/\ |/ |/g'
> ./haba-haba.sh && chmod a+x ./haba-haba.sh
# Динамически генерируем скрипт и запускаем его:
PARAMETERS=`./haba-haba.sh | sed -e 's/ /@/g'`
rm ./haba-haba.sh
HABA2_EXIST=`ls | grep haba-haba2.sh`
if [ "$HABA2_EXIST" != "" ]
then
rm ./haba-haba2.sh
fi
# Найдем таким образом все файлы lnk в папке рекурсивно, включая путь от папки.
i="1"
for PARAMETER in $PARAMETERS
do
if [ "$i" == "2" ]
then
# Если это второй параметр, значит это имя файла в кодировке cp1251, и ее надо перекодировать
# в utf-8, чтобы сохранить имена файлов
PARAMETER=`echo $PARAMETER | iconv -f windows-1251 -t utf8`
echo $PARAMETER >> ./haba-haba2.sh
i="1"
continue
fi
if [ "$i" == "1" ]
then
i="2"
echo $PARAMETER >> ./haba-haba2.sh
continue
fi
done
# Заменяем подстроку на название еще одного скрипта - exchange-lnk.sh
# Единственная задача этого скрипта - Удалить линк-файл и скопировать на его место документ
cat ./haba-haba2.sh
| sed -e 's/FileName:@/./exchange-lnk.sh /'
| sed -e 's/File@name:@//'
| sed -e 's/\///g'
| sed -e 's/.lnk/.lnk \/g'
| sed -e 's/@/\ /g'
| sed -e 's/(/\(/g'
| sed -e 's/)/\)/g'
>./haba-haba.sh
rm ./haba-haba2.sh
sh ./haba-haba.sh
rm ./haba-haba.sh
Второй скрипт, который удаляет ярлык и на его место копирует документ
#!/bin/sh
# remove first parameter and copy second parameter to first parameter's path
LINK_NAME=`echo $1 | sed -e '{s/ /\ /g}' | sed -e 's/(/\(/g'`
LINK_PATH=$(dirname "$LINK_NAME")
echo $LINK_PATH
echo Copy $2 to path of $1
cp "$2" "$LINK_PATH"
rm "$1"
3) cron
Ну, и последнее, что осталось, это добавить в крон задание, чтобы процесс происходил автоматически.
0 4 * * * root /usr/sync/doc-sync.sh
Если есть вопросы, пишите, постараюсь ответить или дополнить/поправить
Автор: 3vi1_0n3