сети
СКАНЕР ПОРТОВ: ПРИМЕР РЕАЛИЗАЦИИ
ВЛАДИМИР МЕШКОВ Введение Подавляющее большинство сетевых служб использует при работе протокол TCP. Согласно модели OSI, ТСР является протоколом транспортного уровня. Он обеспечивает надежное двунаправленное соединение между двумя процессами; данные передаются в обоих направлениях без ошибок, пакеты не теряются и не дублируются, последовательность передачи данных не нарушается.
72
Процесс, получающий или отправляющий данные, идентифицируется на этом уровне номером, который называется номером порта, или просто портом. Другими словами, порт определяет сетевую службу, которой предназначен пакет. Для возможности установления соединения с какой-либо сетевой службой соответствующий ей порт должен быть открыт, или, выражаясь терминологией TCP-соединения, на-
ходится в состоянии LISTEN (прослушиваться). Быстро определить состояние порта позволяет специальное программное обеспечение - сканер портов. Сканирование портов выполняется, как правило, для того, чтобы найти на узле службу, уязвимую с точки зрения безопасности сети. Это своего рода разведка, которая может осуществляться как администратором сети, так и злоумышленником.
сети Целью данной статьи является описание принципов функционирования и внутреннего устройства простого сканера портов TCP протокола.
Порядок установления TCP соединения Для понимания принципа работы сканера необходимо знать, каким образом устанавливается TCP соединение. При установлении соединения задействуются поля «Порядковый номер» (SEQ), «Номер подтверждения» (ACK-SEQ), флаги SYN, ACK и RST заголовка TCP-пакета. Флаг SYN является флагом синхронизации. Он используется при установлении соединения и устанавливает начальный порядковый номер, используемый для последующей передачи данных. Флаг ACK указывает на то, что поле номера подтверждения содержит достоверные данные. Флаг RST используется для сброса соединения. Соединение устанавливается в 3 этапа: n инициатор соединения (клиент) формирует SYN-пакет (TCP-пакет с установленным флагом SYN), заполняет поле SEQ и передает пакет серверу; n если порт, на который пришел запрос на соединение, открыт, сервер формирует SYN|ACK-пакет, заполняет поля SEQ, ACK-SEQ и передает пакет клиенту. Значение поля ACK-SEQ равно значению поля SEQ из пакета клиента, увеличенному на 1 (т.е. ACK-SEQ-сервера = SEQ-клиента + 1). Если порт закрыт, клиенту отправляется RSTпакет; n получив SYN|ACK-пакет, клиент проверяет поле ACK-SEQ и высылает серверу ACK-пакет. После этого соединение считается установленным и переходит в фазу обмена данными между клиентом и сервером.
Методы сканирования Существует достаточно большое число методов сканирования, каждый из которых имеет свои преимущества и, соответственно, недостатки. Подробнее о каждом из них можно про-
№1, октябрь 2002
читать в статье «NESSUS - современный анализ безопасности, методы сканирования»(http://www.hack zone.ru/articles/nessus.htm#scan), а также в документации на сканер Nmap (http://www.insecure.org/nmap). Мы рассмотрим один из методов SYN-сканирование. Этот метод часто называют half-open (полуоткрытым) сканированием, т.к. полное TCP-соединение с портом сканируемой машины не устанавливается. Суть данного метода заключается в следующем. Вы посылаете TCP-пакет с установленным флагом SYN, как если бы собирались установить реальное соединение с выбранным портом, и ожидаете ответ. Принятый пакет с установленными флагами SYN и ACK указывает на то, что выбранный порт открыт и ожидает соединения. Флаг RST означает обратное. Если получен SYN|ACK-пакет, следует немедленно отправить пакет с флагом RST для сброса соединения, хотя реально за вас это сделает ядро. Преимуществом данной технологии является отсутствие в log-файлах сканируемой машины записей о попытках установления соединения с ней. Недостаток необходимость наличия прав root для формирования SYN-пакета.
Пример реализации сканера Приведенный ниже код был разработан и протестирован в ОС GNU/ Linux, дистрибутив Slackware 7.1, компилятор gcc-2.95.2. Алгоритм реализации следующий: n определить необходимые переменные и заголовочные файлы; n создать сокеты для приема и передачи пакетов; n сформировать SYN-пакет и отправить его сканируемому хосту; n принять ответный пакет и проанализировать состояние флагов SYN, ACK, RST; n сделать вывод о статусе проверяемого порта.
Заголовочные файлы и переменные Заголовочные файлы и переменные разместим в файле, который назовем scan.h. Для работы нам пона-
добятся следующие header-файлы: #include #include #include #include #include #include #include #include #include #include #include
<stdio.h> <stdlib.h> <errno.h> <sys/types.h> <sys/socket.h> <sys/ioctl.h>
структуры: struct ifreq *ifr - ñòðóêòóðà äëÿ õðàíåíèÿ ïàðàìåòðîâ ñåòåâîãî èíòåðôåéñà struct iphdr *ih - ñòðóêòóðà, ñîäåðæàùàÿ çàãîëîâîê IP-ïàêåòà sturct tcphdr *th - ñòðóêòóðà, ñîäåðæàùàÿ çàãîëîâîê TCP-ïàêåòà struct sockaddr_in local - ñòðóêòóðà, ñîäåðæàùàÿ àäðåñíóþ èíôîðìàöèþ î ëîêàëüíîé ñèñòåìå struct sockaddr_in dest - ñòðóêòóðà, ñîäåðæàùàÿ àäðåñíóþ èíôîðìàöèþ îá óäàëåííîé ñèñòåìå struct p_header { u_long s_addr; u_long d_addr; u_char zer0; u_char protocol; u_int lenght; } *pseudo - ïñåâäîçàãîëîâîê. Íåîáõîäèì ïðè ðàñ÷åòå êîíòðîëüíîé ñóììû TCPïàêåòà (âçÿò èç èñõîäíûõ òåêñòîâ ñêàíåðà Nmap)
и переменные: int fd - äåñêðèïòîð âñïîìîãàòåëüíîãî ñîêåòà e0_s- äåñêðèïòîð ñîêåòà äëÿ ïåðåäà÷è e0_r- äåñêðèïòîð ñîêåòà äëÿ ïðèåìà sent- ÷èñëî ïåðåäàííûõ áàéò rec- ÷èñëî ïðèíÿòûõ áàéò port- íîìåð ñêàíèðóåìîãî ïîðòà index- èíäåêñ èíòåðôåéñà, ÷åðåç êîòî ðûé îñóùåñòâëÿåòñÿ ñêàíèðîâàíèå. u_char *packet- ïàêåò, ïåðåäàâàåìûé â ñåòü.
Сокет для передачи Дескриптор сокета для передачи получим при помощи функции getsock_send, приведенной ниже. Необходимые заголовочные файлы: #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include #include #include int getsock_send ( char *intf) - âûçîâ ôóíêöèè
Функция getsock_send принимает строковое значение (имя интерфейса) и возвращает дескриптор сокета в случае положительного завершения или -1, если произошла ошибка.
73
сети Переменные: int fd - äåñêðèïòîð ñîêåòà const int on=1 - ôëàã âêëþ÷åíèÿ çàãîëîâêà (ñì. íèæå) struct ifreq ifr - ñòðóêòóðà äëÿ õðàíåíèÿ ïàðàìåòðîâ ñåòåâîãî èíòåðôåéñà.
Сокет создадим следующим системным вызовом: if (( fd = socket ( AF_INET, SOCK_RAW, htons(ETH_P_IP) )) < 0 ) { perror ( «socket» ); return (-1); }
В данном и последующих вызовах мы будем включать код, обрабатывающий возможные ошибки. Комментировать его особого смысла нет, и так все понятно, надеюсь. Сокеты типа SOCK_RAW (RAWсокеты) домена AF_INET удобны тем, что позволяют получить непосредственный доступ к служебным полям протокола TCP/IP, в отличии от типов SOCK_STREAM и SOCK_DGRAM (для TCP и UDP соединений, соответственно). Существуют также пакетные сокеты, о которых я рассказывал в статье «Анализатор сетевого траффика», на них мы сейчас останавливаться не будем. Так как формировать пакеты мы будем вручную, то необходимо в опциях сокета указать данный факт. Делается это при помощи системного вызова setsockopt следующим образом: if ( setsockopt ( fd, IPPROTO_IP, IP_HDRINCL, ( const void *) &on, sizeof ( on ) ) < 0 ) { perror ( «setsockopt IP_HDRINCL» ); close ( fd ); return ( -1 ); }
Опция IP_HDRINCL является опцией протокола IP, поэтому параметр level вызова setsockopt равен IPPROTO_IP. Если опция IP_HDRINCL установлена, приложение строит и вставляет в исходящий пакет полный IP заголовок. Данная опция включается ненулевым значением (const int on=1). Поскольку мы собираемся работать через определенный интерфейс, необходимо осуществить привязку сокета к выбранному интерфейсу. Для этой цели также используется системный вызов setsockopt: sprintf ( ifr.ifr_name, «%s», intf );
74
if ( setsockopt ( fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof (ifr)) <0 ) { perror ( « SO_BINDTODEVICE» ); close ( fd ); return ( -1 ); }
Опция SO_BINDTODEVICE является опцией сокета, параметр level вызова setsockopt принимает значение SOL_SOCKET. Опция SO_BINDTODEVICE использует экземпляр структуры ifreq. Вызов setsockopt считывает из буфера данной структуры имя интерфейса, при помощи которого будут обслуживаться все доступы к рассматриваемому сокету. Поэтому вначале мы заполняем соответствующее поле структуры ifreq именем интерфейса, который был передан как параметр функции getsock_send. Если все успешно, возвращаем в главную функцию дескриптор сокета: return ( fd ).
Сокет для приема Дескриптор сокета для приема получим при помощи функции getsock_recv (пример данной функции был рассмотрен ранее в статье «Анализатор сетевого траффика»). Заголовочные файлы: #include #include #include #include #include
<sys/types.h> <sys/socket.h> <errno.h >
int getsock_recv (int index) - âûçîâ ôóíêöèè
Функция getsock_recv принимает в качестве параметра индекс интерфейса и возвращает дескриптор сокета. Дескриптор сокета: int fd;
Структура для хранения адресной информации об интерфейсе (см. файл ): struct sockaddr_lls_ll;
Создадим пакетный сокет: if (( fd= socket (SOCK_PACKET, SOCK_DGRAM, htons (ETH_P_ALL) )) <0 ) { perror ( «socket» );
}
return ( - 1 );
Пакетный сокет имеет тип SOCK_DGRAM. Это означает, что заголовок физического уровня (MACадрес в случае Ethernet) будет отброшен при приеме. Выделим память для структуры struct sockaddr_ll s_ll: memset (&s_ll, 0, sizeof (struct sockaddr_ll));
Заполним поля структуры s_ll необходимыми значениями: s_ll.sll_family = PF_PACKET; - òèï ñîêåòà s_ll.sll_protocol = htons (ETH_P_ALL); - òèï ïðèíèìàåìîãî ïðîòîêîëà s_ll.sll_ifindex = index; - íîìåð èíòåðôåéñà s_ll.sll_pkttype = PACKET_HOST; - òèï ïàêåòà (äëÿ ëîêàëüíîé ìàøèíû)
Для получения пакетов только с определенного интерфейса используется функция bind: таким образом мы соединяем пакетный сокет с интерфейсом, номер которого указан в структуре struct sockaddr_ll s_ll. Привяжем сокет к интерфейсу: if ((bind (fd, (struct sockaddr *) &s_ll, sizeof (struct sockaddr_ll)) <0 ) { perror («bind»); close (fd); return (-1); }
Возвратим дескриптор сокета в вызывающую функцию: return (fd)
Главная функция #include «scan.h» - ôàéë ñ ïåðåìåííûìè è header-ôàéëàìè int main ( int argc, char *argv [ ] ) - âûçîâ ãëàâíîé ôóíêöèè
Главной функции мы передаем два параметра: IP-адрес сканируемого хоста (аргумент argv[1]) и номер порта (аргумент argv[2]). Обработкой ошибочного ввода параметров будет заниматься функция usage (): void usage () { printf ( « \n scan [ dest_IP ] [ dest_port ] \n» ); return; }
Первое, что необходимо сделать
сети при запуске программы, это проверить, все ли необходимые параметры указаны: if ( argc != 3 ) { usage (); exit ( 1 ); }
Преобразуем введенные строковые значения адреса и порта в сетевой формат и заполним адресную структуру удаленной системы: port = atoi ( argv [2] ); memset ( &dest, 0, sizeof ( struct sockaddr_in )); dest.sin_addr.s_addr = inet_ntoa ( argv [1] ); dest.sin_port = htons ( port );
Тоже самое проделаем для локального хоста. memset ( &local, 0, sizeof ( struct sockaddr_in ));
Получим IP-адрес интерфейса и занесем его в адресную структуру local: );
p_header )); - îáíóëèì ñòðóêòóðó pseudo -> s_addr = local.sin_addr.s_addr; àäðåñ ëîêàëüíîãî õîñòà pseudo -> d_addr = dest.sin_addr.s_addr; àäðåñ óäàëåííîãî õîñòà pseudo -> protocol = 6; - ïðîòîêîë (TCP) pseudo -> lenght = htons ( sizeof ( struct tcphdr )); - äëèíà ïñåâäîçàãîëîâêà
Сформируем TCP заголовок. memset ( th, 0, sizeof ( struct tcphdr )); - îáíóëèì ñòðóêòóðó th -> source = local.sin_port; ëîêàëüíûé ïîðò th -> dest = dest.sin_port; - óäàëåííûé ïîðò th -> seq = htonl ( 1156270349 ); - íà÷àëüíûé ïîðÿäêîâûé íîìåð th -> ack_seq = 0; íîìåð ïîäòâåðæäåíèÿ th -> doff = 5; äëèíà çàãîëîâêà ( â 32-õ ðàçðÿäíûõ ñëîâàõ ) th -> syn = 1; óñòàíîâèòü ôëàã SYN th -> window = htons ( 3072 ); ðàçìåð îêíà th -> check = 0; îáíóëèòü ïîëå êîíòðîëüíîé ñóììû th -> check = in_cksum (( u_short *)pseudo, sizeof ( struct tcphdr) + sizeof ( struct p_header));
fd = socket ( AF_INET, SOCK_DGRAM, 0
sprintf ( ifr -> ifr_name, «%s», «eth0»); octl ( fd, SIOCGIFADDR, ifr ); memcpy (( char *) &local, ( char *)&( ifr -> ifr_addr ), sizeof ( struct sockaddr )); local.sin_port = htons (53);
Получим индекс интерфейса: ioctl ( fd, SIOCGIFINDEX, ifr ); index = ifr -> ifindex;
Выделим память для хранения данных, передаваемых и принимаемых из сети. Пакет будет состоять только из заголовков IP и TCP протоколов. packet = ( u_char * )malloc( sizeof ( struct iphdr ) + sizeof ( struct tcphdr ));
Внутри общего пакета разместим служебные заголовки IP и TCP протоколов, а также псевдозаголовок. in = ( struct iphdr * ) packet; th = ( struct tcphdr * ) (packet + sizeof ( struct iphdr )); pseudo = (struct p_header *) ( packet + sizeof ( struct iphdr) - sizeof ( struct p_header ));
Заполним поля псевдозаголовка необходимыми значениями. memset ( pseudo, 0, sizeof ( struct
№1, октябрь 2002
Алгоритм расчета контрольной суммы для заголовков TCP и IP одинаковый и будет изложен ниже. Сформируем IP заголовок. memset ( ih, 0, sizeof ( struct iphdr )); - îáíóëèì ñòðóêòóðó ih -> version = 4; - âåðñèÿ ïðîòîêîëà ih -> ihl = 5; äëèíà çàãîëîâêà (÷èñëî 32-õ áèòíûõ ñëîâ) ih -> tot_len = htons (sizeof (struct iphdr)+sizeof(struct tcphdr)); - äëèíà ïàêåòà ih -> id = 3290; - ïîðÿäêîâûé íîìåð ïàêåòà (èäåíòèôèêàöèÿ) ih -> ttl = 42; - âðåìÿ æèçíè ih -> protocol = 6; - òðàíñïîðòíûé ïðîòîêîë (TCP) ih -> saddr = local.sin_addr.s_addr; - ëîêàëüíûé àäðåñ ih -> daddr = dest.sin_addr.s_addr; - óäàëåííûé àäðåñ ih -> check = in_cksum (( u_short *) ih, sizeof (struct iphdr )); êîíòðîëüíàÿ ñóììà
Некоторые поля заголовков (например, поле «Начальный порядковый номер» TCP заголовка и поле «Идентификация» заголовка IP) были выбраны совершенно произвольно, т.к. в данном случае эти значения ни на что не влияют. Отобразим для контроля имеющуюся адресную информацию:
printf ( «IP-àäðåñ íàçíà÷åíèÿ \t -\t %s \n «, inet_ntoa ( ih -> daddr )); printf ( «IP-àäðåñ èñòî÷íèêà \t \t %s \n «, inet_ntoa ( ih -> saddr )); printf ( «Ïîðò íàçíà÷åíèÿ \t \t \t %d \n «, ntohs ( th -> dest )); printf ( «Ïîðò èñòî÷íèêà \t\t - \t %d \n «, ntohs ( th -> source ));
Создадим сокеты для передачи и приема пакетов. if (( e0_s = getsock_send ( «eth0» )) < 0 ) { perror ( «getsock_send» ); exit ( 1 ); } if (( e0_r = getsock_recv ( index )) < 0 ) { perror ( «getsock_recv» ); exit ( 1 ); }
Передадим сформированный SYN-пакет хосту назначения. dest.sin_family = AF_INET; sent = sendto ( e0_s, (char *) packet, ntohs ( ih -> tot_len), 0, ( struct sockaddr *)&dest, sizeof (struct sockaddr_in)); if ( sent <= 0 ) { perror ( «sendto» ); exit ( 1 ); } printf ( «\n Ïåðåäàíî %d áàéò \n», sent );
Примем ответ на наш запрос. Прием будем осуществлять в бесконечном цикле, каждый раз обнуляя приемный буфер. for ( ; ; ) { bzero ( packet, sizeof (packet)); rec = 0; rec = recvfrom ( e0_r, (char *) packet, sizeof ( struct iphdr ) + sizeof ( struct tcphdr ), 0, NULL, NULL ); if ( rec <0 || rec > 1500 ) { perror ( «recvfrom» ); exit ( 1 ); }
Число 1500 определяет максимальный размер MTU для сети Ethernet. Больше этого значения в одном пакете принять мы не можем, и любое превышение данного предела трактуется как ошибка. Теперь займемся анализом принятого пакета. Для начала проверим соответствие версии протокола IP. Поле «Версия» должно содержать 4. Если это не так (к нам мог поступить ARPзапрос, который мы не собираемся обрабатывать), то принятый пакет от-
75
сети брасывается и продолжается ожидание: if (( ih -> version ) != 4 ) continue;
Также мы не будем обрабатывать IP-пакеты, отправителем которых не является сканируемый хост: if (( ih -> saddr dest.sin_addr.s_addr ) continue;
!=
Контрольная сумма Расчет контрольной суммы выполняет следующая функция: #include < linux/types.h > __u16 in_cksum ( __u16 *ptr, int nbytes ) { register __u32 sum; __u16 oddbyte; register __u16 answer; sum = 0; while ( nbytes > 1 ) { sum += *ptr ++; nbytes -= 2; }
и если транспортный протокол не есть TCP: if (( ih -> protocol != 6 ) continue;
Если принятый пакет соответствует всем условиям, то нам останется только отобразить результаты: printf ( «Ïðèíÿòî %d áàéò \n \n «, rec ); printf ( «%s \t -> \t « , inet_ntoa ( ih ->saddr )); printf ( «%s \t \n \n «, inet_ntoa ( ih > daddr )); printf ( «Âåðñèÿ \t \t \t = %d \n», ih > version ); printf ( «Äëèíà çàãîëîâêà \t \t = %d \n», ih -> ihl ); printf ( «Äëèíà ïàêåòà \t \t= %d \n», ntohs (ih -> tot_len )); printf ( «Èäåíòèôèêàòîð \t \t = %d \n», ih -> id ); printf ( «Âðåìÿ æèçíè \t \t = %d \n», ih -> ttl ); printf ( «Ïðîòîêîë \t \t = %d \n», ih -> protocol ); printf ( «Êîíòðîëüíàÿ ñóììà IP \t= %d \n», ih -> check ); printf ( «Ïîðò èñòî÷íèê \t \t = %d \n», ntohs ( th -> source )); printf ( «Ïîðò íàçíà÷åíèÿ \t \t = %d \n», ntohs ( th -> dest )); printf ( «Êîíòðîëüíàÿ ñóììà TCP \t = %d \n», th->check); printf ( «SEQ \t \t \t = %lu \n», ntohl ( th -> seq )); printf ( «ACK-SEQ \t \t \t = %lu \n», ntohl ( th -> ack_seq )); if ( th -> syn == 1 ) printf ( «Ôëàã SYN óñòàíîâëåí \n» ); if ( th -> ack == 1 ) printf ( «Ôëàã ACK óñòàíîâëåí \n» ); if ( th -> fin == 1 ) printf ( «Ôëàã FIN óñòàíîâëåí \n» ); if ( th -> rst == 1 ) printf ( «Ôëàã RST óñòàíîâëåí \n» ); if ( th -> psh == 1 ) printf ( «Ôëàã PUSH óñòàíîâëåí \n» ); if ( th -> urg == 1 ) printf ( «Ôëàã URG óñòàíîâëåí \n» ); if (( th -> syn == 1 )&&(th->ack==1)) printf («Ïîðò %d îòêðûò \n», ntohs (th > source ));
Здесь все предельно ясно. После этого мы прерываем цикл приема пакетов и выходим из программы. break; } return (1); }
Сброс соединения возложим на ядро.
76
if ( nbytes == 1 ) { oddbytes = 0; * (( unsigned char *) &oddbyte ) = * (unsigned char *) ptr; sum += oddbyte; } & 0xFFFF);
}
sum = ( sum >> 16 ) + ( sum sum += (sum >> 16 ); answer=~sum; return (answer);
Код для расчета контрольной суммы взят из исходных текстов сканера Nmap, поэтому приводится без комментариев. Порядок расчета контрольной суммы изложен в RFC 1071.
Makefile Для сборки выполняемого модуля создадим Makefile следующего содержания: CC = gcc name = scan SCAN = scan.o checksum.o getsock_send.o getsock_recv.o $( name ) : $( SCAN ) $( CC ) -g -o $( name ) $( NAME ) scan.o : scan.c $( CC ) -c scan.c checksum.o : checksum.c $( CC ) -c checksum.c getsock_send.o : getsock_send.c $( CC ) -c getsock_send.c getsock_recv.o : getsock_recv.c $( CC ) -c getsock_recv.c clean: rm -f *.o
Для получения исполняемого модуля достаточно ввести команду make. После этого в каталоге, где размещены файлы программы, появиться файл scan. При запуске в командной строке модуля необходимо указать IP-адрес сканируемого хоста и номер проверяемого порта. Вот в принципе и все. Обо всех замечаниях и пожеланиях пишите на [email protected].
FAQ сети PERL Можно ли скомпилировать из Perl исполняемый файл? Вы можете воспользоваться программой Perl2Exe. Это утилита для преобразования Perl сценариев в выполняемые файлы, не требующие присутствия интерпретатора языка Perl. Perl2Exe может сгенерировать модули для Win32 и многих клонов Unix. Perl2Exe также позволяет Вам создавать не консольные программы, с использованием Tk. ht tp://www.indigostar.com/ perl2exe.htm — разработчик — IndigoSTAR Software. Еще один продукт IndigoSTAR Software — SendMail for Windows(TM) — Windows версия популярной программы Unix Sendmail. Она позволяет отправлять сообщений из командной строки, CGI сценария или BAT-файла.
Proc::Background — Общий ли для Unix и Win32 интерфейс управление фоновыми процессами? Это общий интерфейс для управления фоновыми процессами как на Unix, так и на Win32 платформах. Модуль позволяет Вам запускать и завершать фоновые процессы, получать выходные данные и отслеживать состояние фоновых процессов. P.S. Рекомендую при использовании под Win32 брать архив со CPAN и посмотреть прилагаемые примеры и скрипты. Proc::Background — http:// search.cpan.org/search?dist=ProcBackground
Как можно стандартизировать (оформить в виде процедуры) получение выборки из БД, чтобы получать набор записей с именованными полями? Это можно сделать так: sub QueryArrayOfHashes my ($DB, $query) = @_; my ($result,$data_hash,@items,$key, $val,%hash); $result = $DB->prepare($query); $result->execute or return; while ($data_hash=$result>fetchrow_hashref)
}
%hash=%$data_hash; push @items,{%hash}; } $result->finish; @items;
Комментарии: $data_hash - ññûëêà íà õýø %$data_hash == %{$data_hash} - ïîëó÷åíèå ñàìîãî õýøà èç ññûëêè {%hash} = ðàçèìåíîâàííûé õýø - ÷òîá ïîëó÷èëñÿ ìàññèâ õýøåé, à íå ïðîñòî îäèí ìàññèâ @items â äàííîì ñëó÷àå == return @items Ïðèìåð èñïîëüçîâàíèÿ: use DBI; ... $dbh=DBI->connect(DBI:mysql:mysql: localhost, $user, $password, {RaiseError => 1}) or die «connecting : $DBI::errstr\n»; @res = QueryArrayOfHashes($dbh, «select user, password from user»); for ($i=0; $i<=$#res; $i++) { print «\n[Record #$i]::\n»; foreach $key (sort keys %{$res[$i]}) { # çàïèñü âèäà $a[1]{b} ýêâèâàëåíòíà $a[1]->{b} print $key, «\t», $res[$i]{$key}, «\n»; } } $dbh->disconnect;
Как удалить дерево каталогов? В «Perl Cookbook» by Tom Christiansen and Nathan Torkington, O’Reilly («Библиотека программиста: Perl», издательство «Питер»), приводится два примера рекурсивного удаления каталога вместе с его содержимым. В одном используется функция finddepth из модуля File::Find, во втором - функция rmtree из File::Path. Вот еще один способ: # в качестве параметров скрипт принимает # список директорий для удаления die «usage: $0 [ ... ]\n» unless @ARGV; foreach $path (@ARGV) { del_folder($path); } sub del_folder { my $dir=shift; return 0 unless $dir; my (@dirs,@files,$filename, $newdir,$list); opendir(DIR,$dir) or (warn «Cant rmdir $dir: $!» and return 0); @dirs=grep {!(/^\./) && -d «$dir/ $_»} readdir(DIR); rewinddir(DIR); @files=grep {!(/^\.(\.)?$/) && -f «$dir/$_»} readdir(DIR); closedir (DIR); for $list(0..$#dirs) { $newdir=$dir.»/».$dirs[$list]; del_folder($newdir); } for $list(0..$#files) { $filename=$dir.»/».$files[$list]; unlink $filename or (warn «Cant unlink $filename: $!» and next); } rmdir $dir or (warn «Cant rmdir $dir: $!» and return 0); return 1; }
по материалам www.xpoint.ru составил Дмитрий Горяинов
№1, октябрь 2002
77