|
Ocaml/FAQ/Internet Services
Материал из Wiki.crossplatform.ru
[править] 18. Internet Services
[править] Simple DNS Lookups
#load "unix.cma";;
let () =
try
let addresses = Unix.gethostbyname name in
let addresses =
Array.map Unix.string_of_inet_addr addresses.Unix.h_addr_list in
(* addresses is an array of IP addresses *)
Array.iter print_endline addresses
with Not_found ->
Printf.printf "Can't resolve %s\n" name
(*-----------------------------*)
let () =
try
let host = Unix.gethostbyaddr (Unix.inet_addr_of_string address) in
let name = host.Unix.h_name in
(* name is the hostname ("www.perl.com") *)
print_endline name
with Not_found ->
Printf.printf "Can't resolve %s\n" address
(*-----------------------------*)
let () =
try
let host = Unix.gethostbyaddr (Unix.inet_addr_of_string address) in
let name = host.Unix.h_name in
try
let addresses = Unix.gethostbyname name in
let addresses =
Array.map Unix.string_of_inet_addr addresses.Unix.h_addr_list in
Array.iter print_endline addresses;
let found = List.mem address (Array.to_list addresses) in
print_endline (if found then "found" else "not found")
with Not_found ->
Printf.printf "Can't look up %s\n" name
with Not_found ->
Printf.printf "Can't look up %s\n" address
(*-----------------------------*)
#!/usr/bin/ocaml
(* mxhost - find mx exchangers for a host *)
(* Though there is an experimental new DNS resolver for OCaml called
Netdns, it does not yet support resolving MX records. For now, we'll
use Net::DNS through perl4caml until a better solution is available.
*)
#directory "+perl";;
#load "perl4caml.cma";;
let _ = Perl.eval "use Net::DNS"
let host = Sys.argv.(1)
let res = Perl.call_class_method "Net::DNS::Resolver" "new" []
let mx = Perl.call_array ~fn:"mx" [res; Perl.sv_of_string host]
let () =
if mx = [] then
Printf.eprintf "Can't find MX records for %s (%s)\n"
host (Perl.string_of_sv (Perl.call_method res "errorstring" []))
let () =
List.iter
(fun record ->
let preference = Perl.call_method record "preference" [] in
let exchange = Perl.call_method record "exchange" [] in
Printf.printf "%s %s\n"
(Perl.string_of_sv preference)
(Perl.string_of_sv exchange))
mx
(*-----------------------------*)
#!/usr/bin/ocaml
(* hostaddrs - canonize name and show addresses *)
#load "unix.cma";;
let name = Sys.argv.(1)
let hent = Unix.gethostbyname name
let () =
Printf.printf "%s => %s\n"
hent.Unix.h_name (* in case different *)
(String.concat " "
(Array.to_list
(Array.map
Unix.string_of_inet_addr
hent.Unix.h_addr_list)))
[править] Being an FTP Client
(* The Netclient package from Ocamlnet provides an event-driven
FTP client. This client does not currently support uploading.
Ocamlnet is available here:
http://projects.camlcity.org/projects/ocamlnet.html
This recipe assumes it has been installed with findlib. *)
#use "topfind";;
#require "netclient";;
(* Create an FTP client instance. *)
let ftp = new Ftp_client.ftp_client ()
(* Build and execute a chain of FTP methods. *)
let () =
ftp#add (new Ftp_client.connect_method ~host:"127.0.0.1" ());
ftp#add (new Ftp_client.login_method
~user:"anonymous"
~get_password:(fun () -> "user@example.com")
~get_account:(fun () -> "anonymous") ());
ftp#add (new Ftp_client.walk_method (`Dir "/pub"));
let ch = new Netchannels.output_channel (open_out "output.txt") in
ftp#add (new Ftp_client.get_method
~file:(`Verbatim "index.txt")
~representation:`Image
~store:(fun _ -> `File_structure ch) ());
ftp#run ()
(*-----------------------------*)
(* If an error occurs, it will be exposed by the "state" property. *)
let () =
match ftp#state with
| `Error (Ftp_client.FTP_error (Unix.Unix_error (e, _, _))) ->
Printf.eprintf "Error: %s\n%!"
(Unix.error_message e)
| _ -> ()
(*-----------------------------*)
(* To determine the current working directory, send invoke the `PWD
command and inspect the result in a callback. *)
let () =
ftp#add (new Ftp_client.invoke_method
~command:`PWD
~process_result:(fun state (code, message) ->
Printf.printf
"I'm in the directory %s\n%!"
message) ())
(*-----------------------------*)
(* Use mkdir_method and rmdir_method to make and remove directories from
the remote server. Use the optional ~onerror argument to specify an
error handler. *)
let () =
ftp#add
~onerror:(fun e ->
Printf.eprintf "Can't create /ocaml: %s\n%!"
(Printexc.to_string e))
(new Ftp_client.mkdir_method (`Verbatim "/pub/ocaml"))
(*-----------------------------*)
(* Use a list_method to get a list of files in a remote directory. *)
let () =
let buffer = Buffer.create 256 in
let ch = new Netchannels.output_buffer buffer in
ftp#add
~onsuccess:(fun () -> print_endline (Buffer.contents buffer))
~onerror:(fun e ->
Printf.eprintf "Can't get a list of files in /pub: %s\n%!"
(Printexc.to_string e))
(new Ftp_client.list_method
~dir:(`Verbatim "/pub")
~representation:`Image
~store:(fun _ -> `File_structure ch) ())
(*-----------------------------*)
(* Use `QUIT followed by ftp#abort to close the connection and exit
the event loop. *)
let () =
ftp#add (new Ftp_client.invoke_method
~command:`QUIT
~process_result:(fun _ _ -> ftp#abort ()) ())
[править] Sending Mail
(* Use Netsendmail, part of the Netstring package that comes with
Ocamlnet, to send mail through a command-line mailer program. *)
#use "topfind";;
#require "netstring";;
let () =
Netsendmail.sendmail
~mailer:"/usr/sbin/sendmail" (* defaults to "/usr/lib/sendmail" *)
(Netsendmail.compose
~from_addr:(from_name, from_address)
~to_addrs:[(to_name, to_address)]
~subject:subject
body)
(*-----------------------------*)
(* You can also open a pipe directly to sendmail. *)
#load "unix.cma";;
let () =
let sendmail =
Unix.open_process_out "/usr/lib/sendmail -oi -t -odq" in
output_string sendmail "\
From: User Originating Mail <me@host>
To: Final Destination <you@otherhost>
Subject: A relevant subject line
Body of the message goes here, in as many lines as you like.
";
ignore (Unix.close_process_out sendmail)
[править] Reading and Posting Usenet News Messages
(* There is no NNTP library available for OCaml. With a little
preparation, we can easily use the one that comes with Perl
using perl4caml (http://merjis.com/developers/perl4caml) *)
#directory "+perl";;
#load "perl4caml.cma";;
module NNTP = struct
open Perl
let _ = eval "use Net::NNTP"
(* Returned by "list" method so that newsgroups stay sorted. *)
module GroupMap = Map.Make(String)
(* Wrapper for Net::NNTP class. *)
class nntp host =
let nntp =
call_class_method "Net::NNTP" "new" [sv_of_string host] in
(* Raise a Failure exception if we couldn't connect. *)
let () =
if sv_is_undef nntp
then failwith (string_of_sv (eval "$!")) in
(* Helper function to transform nullable string arrays to OCaml. *)
let maybe_string_list sv =
if sv_is_undef sv
then raise Not_found
else List.map string_of_sv (list_of_av (deref_array sv)) in
object (self)
val nntp = nntp
method group name =
match call_method_array nntp "group" [sv_of_string name] with
| [narticles; first; last; name] ->
(int_of_sv narticles, int_of_sv first,
int_of_sv last, string_of_sv name)
| _ -> raise Not_found
method head msgid =
maybe_string_list (call_method nntp "head" [sv_of_int msgid])
method body msgid =
maybe_string_list (call_method nntp "body" [sv_of_int msgid])
method article msgid =
maybe_string_list (call_method nntp "article" [sv_of_int msgid])
method postok () =
bool_of_sv (call_method nntp "postok" [])
method post lines =
let lines = List.map sv_of_string lines in
if (sv_is_undef (call_method nntp "post" lines))
then failwith (string_of_sv (eval "$!"))
method list () =
let hv = deref_hash (call_method nntp "list" []) in
let map = ref GroupMap.empty in
List.iter
(fun (name, info) ->
map :=
GroupMap.add
name
(match list_of_av (deref_array info) with
| [last; first; flags] ->
(int_of_sv last, int_of_sv first,
string_of_sv flags)
| _ -> assert false)
!map)
(assoc_of_hv hv);
!map
method quit () =
ignore (call_method nntp "quit" [])
end
end
(*-----------------------------*)
(* Connect to an NNTP server by creating an "nntp" object. *)
let server =
try new NNTP.nntp "news.west.cox.net"
with Failure s ->
Printf.eprintf "Can't connect to news server: %s\n" s;
exit 1
(*-----------------------------*)
(* Select a newsgroup and retrieve its stats. *)
let (narticles, first, last, name) =
try server#group "misc.test"
with Not_found ->
Printf.eprintf "Can't select misc.test\n";
exit 1
(*-----------------------------*)
(* Get the headers from the last article. *)
let headers =
try server#head last
with Not_found ->
Printf.eprintf "Can't get headers from article %d in %s\n"
last name;
exit 1
(*-----------------------------*)
(* Get the body from the last article. *)
let body =
try server#head last
with Not_found ->
Printf.eprintf "Can't get body from article %d in %s\n"
last name;
exit 1
(*-----------------------------*)
(* Get the headers and body from the last article. *)
let article =
try server#head last
with Not_found ->
Printf.eprintf "Can't get article from article %d in %s\n"
last name;
exit 1
(*-----------------------------*)
(* Determine if posting is allowed with this server. *)
let () =
if not (server#postok ())
then Printf.eprintf "Server didn't tell me I could post.\n"
(*-----------------------------*)
(* Post a message. *)
let () =
begin
try server#post lines
with Failure s ->
Printf.eprintf "Can't post: %s\n" s;
exit 1
end
(*-----------------------------*)
(* Get the complete list of newsgroups. *)
let () =
let groupmap = server#list () in
NNTP.GroupMap.iter
(fun group (last, first, flags) ->
if flags = "y"
then (* I can post to [group] *) ())
groupmap
[править] Reading Mail with POP3
(* Use Netpop, which is part of Ocamlnet. *)
#use "topfind";;
#require "pop";;
(* To create a Netpop client, you need to look up the server address
and build a network connection first. Netpop uses wrappers called
Netchannels to abstract the input and output channels. *)
let inet_addr =
(Unix.gethostbyname mail_server).Unix.h_addr_list.(0)
let addr = Unix.ADDR_INET (inet_addr, Netpop.tcp_port)
let ic, oc = Unix.open_connection addr
let pop =
new Netpop.client
(new Netchannels.input_channel ic)
(new Netchannels.output_channel oc)
let () =
pop#user username;
pop#pass password
(* Messages are retreived as a hashtable from message IDs to tuples,
each tuple containing the message size in bytes and a string of
server-specific extension data. *)
let messages = pop#list ()
let () =
Hashtbl.iter
(fun msgid (size, ext) ->
let message = pop#retr msgid in
(* message is a Netchannels.in_obj_channel *)
pop#dele msgid)
messages
(*-----------------------------*)
(* Use pop#apop instead of pop#user/pop#pass to avoid sending passwords
in plaintext across the network. *)
let () = pop#apop username password
(*-----------------------------*)
(* Get a message by number and print it to the console. *)
let () =
Printf.printf "Retrieving %d : %!" msgnum;
try
let message = pop#retr msgnum in
print_newline ();
print_endline
(Netchannels.string_of_in_obj_channel message)
with Netpop.Err_status e ->
Printf.printf "failed (%s)\n%!" e
(*-----------------------------*)
(* Gracefully tear down the connection. *)
let () =
pop#quit ();
Unix.shutdown_connection ic;
close_out oc
[править] Simulating Telnet from a Program
(* To simulate a Telnet client with OCaml, you can use the
Telnet_client module from Ocamlnet's "netclient" package.
This module is written in an asynchronous style, so you
will need to create event handlers to process the Telnet
events that occur: data, end of file, timeout, and the
sending and receiving of options (also known as "do",
"don't", "will", and "won't". *)
#use "topfind";;
#require "netclient";;
open Telnet_client
(* This class wraps the Telnet session for convenience in
defining event handlers and chaining them together. *)
class session ~host ~port ~username ~password ~prompt ~timeout =
object (self)
(* Telnet_client.telnet_session instance to wrap. *)
val telnet = new telnet_session
(* Initial on-data handler, which will be redefined later. *)
val mutable process = fun _ -> ()
(* Initialize the Telnet session. *)
initializer
telnet#set_connection (Telnet_connect (host, port));
telnet#set_options {connection_timeout=timeout;
verbose_connection=false;
verbose_input=false;
verbose_output=false};
telnet#set_callback self#on_input;
telnet#set_exception_handler self#on_exception;
telnet#attach ();
process <- self#start
(* Build an input callback that checks for a regular
expression match in the input and calls a callback
function if the match is positive. *)
method waitfor pat cb =
let rex = Pcre.regexp pat in
fun data -> if Pcre.pmatch ~rex data then cb data
(* Enqueue a line of data and flush the output queue. *)
method write data =
Queue.add (Telnet_data data) telnet#output_queue;
Queue.add (Telnet_data "\n") telnet#output_queue;
telnet#update ()
(* Handle first input: wait for a login prompt and then
invoke self#send_username to send the username. *)
method start =
self#waitfor "ogin:" self#send_username
(* Send the username and wait for the password prompt. *)
method send_username data =
self#write username;
process <- self#waitfor "assword:" self#send_password
(* Send the password and wait to see if we succeeded. *)
method send_password data =
self#write password;
process <- self#verify_login
(* Determine if the login was a success or a failure.
Abort with an exception on failure; call self#logged_in
on success. *)
method verify_login data =
if Pcre.pmatch ~pat:"incorrect" data
then failwith "Login failed"
else if Pcre.pmatch ~pat:"^\\s*$" data
then () (* ignore blank lines *)
else self#logged_in data
(* Logged in successfully. Wait for a prompt if necessary
and call self#run_ls to send the first command. *)
method logged_in data =
process <- self#waitfor prompt self#run_ls;
self#waitfor prompt self#run_ls data
(* Do a directory listing and wait for results. *)
method run_ls data =
self#write "/bin/ls -1";
process <- self#gather_files
(* This variable will buffer the results of the "ls" command. *)
val mutable files = ""
(* Buffer the filenames printed out from the "ls" command and
print them out once we get a prompt. *)
method gather_files data =
if Pcre.pmatch ~pat:prompt data
then
begin
files <- Pcre.replace ~pat:"^/bin/ls -1\\s*" files;
Printf.printf
"Files: %s\n%!"
(String.concat ", "
(Pcre.split ~pat:"\\s+" files));
self#run_top data
end
else files <- files ^ data
(* Run another command until we get a prompt and then call
self#close to close the connection. *)
method run_top data =
self#write "top -n1 -b";
process <- self#waitfor prompt self#close
(* Close the connection by sending an EOF. *)
method close data =
Queue.add Telnet_eof telnet#output_queue
(* When we receive an EOF, exit the program. *)
method on_eof () =
prerr_endline "EOF";
exit 0
(* If a timeout event is received, exit with an error code. *)
method on_timeout () =
prerr_endline "Timeout";
exit 1
(* Print any thrown exceptions to standard error. *)
method on_exception exn =
prerr_endline (Printexc.to_string exn)
(* This is the main error handler, which dispatches on
Telnet_client events. *)
method on_input got_synch =
while not (Queue.is_empty telnet#input_queue) do
let tc = Queue.take telnet#input_queue in
match tc with
| Telnet_data data -> process data
| Telnet_eof -> self#on_eof ()
| Telnet_timeout -> self#on_timeout ()
| Telnet_will _
| Telnet_wont _
| Telnet_do _
| Telnet_dont _ ->
(* The telnet_session handles these events.
Calling this method is necessary. *)
telnet#process_option_command tc
| _ -> ()
done
(* Run the Telnet session by calling the "run" method on
the underling telnet_session instance. *)
method run = telnet#run
end
(* Create an instance of our custom session class. *)
let session =
new session
~host:"localhost"
~port:23
~username:"test"
~password:"pleac"
~prompt:"\\$ $"
~timeout:10.
(* Start the session. *)
let () = session#run ()
[править] Pinging a Machine
#!/usr/bin/ocaml
(* ping - send and receive ICMP echo packets *)
(* There do not appear to be any libraries available for pinging
servers from OCaml, ICMP or otherwise. In this recipe, we will
make a diversion from the Perl recipe, which simply determines
if a host is up, and instead write a lookalike for the "ping"
shell command. We might as well, if we're going to all the
trouble of building ICMP packets directly. *)
(* Import Unix and enable threads using findlib for convenience. *)
#use "topfind";;
#require "unix";;
#thread;;
(* The Packet module defines a data type and operations for building,
parsing, and checking the integrity of ICMP packets. *)
module Packet = struct
exception Invalid_length of int
exception Invalid_checksum of int * int
(* type' and code define the ICMP message type. An echo message
has type'=8, code=0, and an echo reply has type'=0, code=0.
The id is a unique identifier for the current process to help
distinguish between replies for other processes. seq is the
sequence number, which is usually incremented with each message.
data is the message body whose contents depend on the type of
message. *)
type t = { type' : int;
code : int;
id : int;
seq : int;
data : string }
(* Define a convenience function for constructing packets. *)
let make ?(type'=8) ?(code=0) ~id ~seq data =
{type'=type'; code=code; id=id; seq=seq; data=data}
(* Calculate a checksum for a message by adding its contents, two
bytes at a time, folding the high order bits into the low order
bits, and taking the logical complement. The result will be an
int with 16-bit precision. *)
let checksum s =
let num_bytes = String.length s in
let num_shorts = num_bytes / 2 in
let rec sum_shorts i sum =
if i < num_shorts then
let short = Int32.of_int (int_of_char s.[i * 2] lsl 8
+ int_of_char s.[i * 2 + 1]) in
sum_shorts (i + 1) (Int32.add sum short)
else sum in
let sum = sum_shorts 0 0l in
let sum =
if num_bytes mod 2 = 1 then
Int32.add sum
(Int32.of_int (int_of_char s.[num_bytes - 1] lsl 8))
else sum in
let sum =
Int32.add
(Int32.shift_right sum 16)
(Int32.logand sum 0xffffl) in
Int32.to_int
(Int32.logand
(Int32.lognot (Int32.add (Int32.shift_right sum 16) sum))
0xffffl)
(* Convert a packet to a string that can be sent over a socket. *)
let to_string {type'=type'; code=code; id=id; seq=seq; data=data} =
let b = Buffer.create 20 in
Buffer.add_char b (char_of_int type');
Buffer.add_char b (char_of_int code);
Buffer.add_char b '\000'; (* checksum hi *)
Buffer.add_char b '\000'; (* checksum lo *)
Buffer.add_char b (char_of_int (id lsr 8 land 0xff));
Buffer.add_char b (char_of_int (id land 0xff));
Buffer.add_char b (char_of_int (seq lsr 8 land 0xff));
Buffer.add_char b (char_of_int (seq land 0xff));
Buffer.add_string b data;
let packet = Buffer.contents b in
let sum = checksum packet in
packet.[2] <- char_of_int (sum lsr 8 land 0xff);
packet.[3] <- char_of_int (sum land 0xff);
packet
(* Parse a string into a packet structure. If the string is less than
8 bytes long, an Invalid_length exception will be raised. If the
checksum does not match the contents, an Invalid_checksum
exception will be raised. *)
let of_string s =
if String.length s < 8 then raise (Invalid_length (String.length s));
let s' = String.copy s in
s'.[2] <- '\000';
s'.[3] <- '\000';
let sum = int_of_char s.[2] lsl 8 + int_of_char s.[3] in
let sum' = checksum s' in
if sum <> sum' then raise (Invalid_checksum (sum, sum'));
{type'=int_of_char s.[0];
code=int_of_char s.[1];
id=int_of_char s.[4] lsl 8 + int_of_char s.[5];
seq=int_of_char s.[6] lsl 8 + int_of_char s.[7];
data=String.sub s 8 (String.length s - 8)}
end
(* Define a data structure for the message body of our echo requests. *)
type payload = { timestamp : float; data : string }
(* Send a single ICMP echo request to the given socket and address. *)
let ping socket sockaddr id seq =
let payload =
Marshal.to_string {timestamp=Unix.gettimeofday ();
data="abcdefghijklmnopqrstuvwxyz0123456"} [] in
let message = Packet.to_string (Packet.make ~id ~seq payload) in
ignore
(Unix.sendto socket message 0 (String.length message) [] sockaddr)
(* Loop forever waiting for echo replies, printing them to the
console along with their hostname, IP, and round-trip time. *)
let pong socket id =
let buffer = String.make 256 '\000' in
while true do
let length, sockaddr =
Unix.recvfrom socket buffer 0 (String.length buffer) [] in
let response =
Packet.of_string (String.sub buffer 20 (length - 20)) in
match sockaddr, response with
| Unix.ADDR_INET (addr, port),
{Packet.type'=0; code=0; id=id'; seq=seq; data=data}
when id = id' ->
let host_entry = Unix.gethostbyaddr addr in
let payload = Marshal.from_string data 0 in
Printf.printf
"%d bytes from %s (%s): icmp_seq=%d time=%.3f ms\n%!"
(String.length data)
host_entry.Unix.h_name
(Unix.string_of_inet_addr addr)
seq
((Unix.gettimeofday () -. payload.timestamp) *. 1000.)
| _ -> ()
done
(* Read hostname from command line. *)
let host =
if Array.length Sys.argv <> 2
then (Printf.eprintf "Usage: %s host\n" Sys.argv.(0); exit 1)
else Sys.argv.(1)
(* Use DNS to find the IP address and canonical name. *)
let name, addr =
try
let h = Unix.gethostbyname host in
h.Unix.h_name, h.Unix.h_addr_list.(0)
with Not_found ->
Printf.eprintf "%s: unknown host %s\n" Sys.argv.(0) host;
exit 2
(* Make sure we are running as root, since this is required to
open a socket with SOCK_RAW and send ICMP packets. *)
let () =
if Unix.getuid () <> 0
then (Printf.eprintf "%s: icmp ping requires root privilege\n"
Sys.argv.(0);
exit 3)
(* Start the ping loop. *)
let () =
Printf.printf "PING %s (%s)\n" name (Unix.string_of_inet_addr addr);
(* Build a socket and destination address. *)
let proto = (Unix.getprotobyname "icmp").Unix.p_proto in
let socket = Unix.socket Unix.PF_INET Unix.SOCK_RAW proto in
let sockaddr = Unix.ADDR_INET (addr, 0) in
(* Use the PID as the ID for packets, and create a counter for
the sequence number. *)
let id = Unix.getpid () in
let seq = ref 0 in
(* Start a background thread to print the echo replies. *)
ignore (Thread.create (pong socket) id);
(* Loop forever sending echo requests and sleeping. *)
while true do
incr seq;
ping socket sockaddr id !seq;
Unix.sleep 1
done
[править] Using Whois to Retrieve Information from the InterNIC
(* WHOIS servers depend on the TLD, and their output formats are
informal, inconsistent, and completely different from server
to server. This makes a general solution very large and ad-hoc.
The Net::Whois package, on which the original Perl recipe was
based, no longer works since WHOIS servers started redirecting
to other servers for most of the information.
Since no libraries are available for this task, we will do a
WHOIS lookup manually using sockets. This example shows how to
perform a WHOIS lookup for the "sourceforge.net" domain, and
probably will not work without modification for domains under
any other TLD. *)
#load "unix.cma";;
#load "str.cma";;
let domain_name = "sourceforge.net"
let whois_server = "whois.internic.net"
let service = Unix.getservbyname "whois" "tcp"
let ltrim =
let re = Str.regexp "^[ \r\n\t\x00\x0B]*" in
Str.global_replace re ""
let () =
(* Connect to the parent server to find the redirect. *)
let host = Unix.gethostbyname whois_server in
let socket_in, socket_out =
Unix.open_connection
(Unix.ADDR_INET (host.Unix.h_addr_list.(0),
service.Unix.s_port)) in
output_string socket_out domain_name;
output_string socket_out "\n";
flush socket_out;
let whois_redirect_regexp = Str.regexp "Whois Server: \\(.*\\)" in
let whois_redirect = ref "" in
begin
try
while true do
let line = ltrim (input_line socket_in) in
if Str.string_match whois_redirect_regexp line 0
then whois_redirect := Str.matched_group 1 line
done
with End_of_file ->
Unix.shutdown_connection socket_in
end;
if !whois_redirect = ""
then failwith "Couldn't find WHOIS redirect";
(* Connect to the real server and get the WHOIS data. *)
let host = Unix.gethostbyname !whois_redirect in
let socket_in, socket_out =
Unix.open_connection
(Unix.ADDR_INET (host.Unix.h_addr_list.(0),
service.Unix.s_port)) in
output_string socket_out domain_name;
output_string socket_out "\n";
flush socket_out;
let domain_name_regexp = Str.regexp "Domain name: \\(.*\\)" in
let domain_name = ref "" in
let registrant_regexp = Str.regexp "Registrant:" in
let registrant_name = ref "" in
let registrant_address = ref [] in
let registrant_country = ref "" in
let contact_regexp = Str.regexp "\\(.*\\) Contact:" in
let contacts = ref [] in
begin
try
while true do
let line = ltrim (input_line socket_in) in
if Str.string_match domain_name_regexp line 0
then domain_name := Str.matched_group 1 line
else if Str.string_match registrant_regexp line 0
then
begin
(* Read registrant data. *)
registrant_name := ltrim (input_line socket_in);
let finished = ref false in
while not !finished do
let line = ltrim (input_line socket_in) in
if String.length line > 2
then registrant_address := !registrant_address @ [line]
else if String.length line = 2
then registrant_country := line
else finished := true
done
end
else if Str.string_match contact_regexp line 0
then
begin
(* Read contact data. *)
let contact_type = Str.matched_group 1 line in
let contact_info = ref [] in
for i = 1 to 6 do
let line = ltrim (input_line socket_in) in
contact_info := !contact_info @ [line]
done;
contacts := (contact_type, !contact_info) :: !contacts
end
done
with End_of_file ->
Unix.shutdown_connection socket_in
end;
(* Display the results. *)
Printf.printf "The domain is called %s\n" !domain_name;
Printf.printf "Mail for %s should be sent to:\n" !registrant_name;
List.iter (Printf.printf "\t%s\n") !registrant_address;
Printf.printf "\t%s\n" !registrant_country;
if !contacts = []
then Printf.printf "No contact information.\n"
else
begin
Printf.printf "Contacts:\n";
List.iter
(fun (contact_type, contact_info) ->
Printf.printf " %s\n" contact_type;
List.iter (Printf.printf " %s\n") contact_info)
!contacts
end
[править] Program: expn and vrfy
#!/usr/bin/ocaml
(* expn -- convince smtp to divulge an alias expansion *)
#use "topfind";; (* Findlib *)
#require "str";; (* Stdlib *)
#require "unix";; (* Stdlib *)
#require "perl";; (* Perl4caml *)
#require "smtp";; (* Ocamlnet *)
let _ = Perl.eval "use Net::DNS" (* Net::DNS *)
let selfname = Unix.gethostname ()
let () =
if Array.length Sys.argv < 2
then (Printf.eprintf "usage: %s address@host ...\n" Sys.argv.(0);
exit 1)
let () =
List.iter
(fun combo ->
let name, host =
match Str.bounded_split (Str.regexp "@") combo 2 with
| [] -> "", ""
| [name] -> name, "localhost"
| [name; host] -> name, host
| _ -> assert false in
let hosts =
Perl.call_array ~fn:"mx" [Perl.sv_of_string host] in
let hosts =
List.map (fun mx -> Perl.call_method mx "exchange" []) hosts in
let hosts =
if hosts = [] then [Perl.sv_of_string host] else hosts in
List.iter
(fun host ->
let host = Perl.string_of_sv host in
Printf.printf "Expanding %s at %s (%s): %!"
name host combo;
let inet_addr =
(Unix.gethostbyname host).Unix.h_addr_list.(0) in
let addr = Unix.ADDR_INET (inet_addr, Netsmtp.tcp_port) in
try
let ic, oc = Unix.open_connection addr in
let smtp =
new Netsmtp.client
(new Netchannels.input_channel ic)
(new Netchannels.output_channel oc) in
ignore (smtp#helo ~host:selfname ());
print_endline
(match smtp#expn name with
| None -> "None"
| Some results -> String.concat ", " results);
smtp#quit ();
Unix.shutdown_connection ic;
close_out oc
with Unix.Unix_error (Unix.ECONNREFUSED, _, _) ->
Printf.eprintf "cannot connect to %s\n" host)
hosts)
(List.tl (Array.to_list Sys.argv))
|