#!/usr/bin/perl # # asip-status - send DSIGetStatus to an AppleShare IP file server (aka # ASIP, aka AFP over TCP port 548). A returned UAM of # "No User Authen" means that the server supports guest access. # # author: James W. Abendschan # license: GPL - http://www.gnu.org/copyleft/gpl.html # url: http://www.jammed.com/~jwa/hacks/security/asip/ # date: 7 May 1997 (v1.0) # see also: # - http://developer.apple.com/techpubs/macos8/NetworkCommSvcs/AppleShare/ # - http://www2.opendoor.com/asip/ (excellent Mac sharing / security site) # # todo: log in as guest & get a list of shares # # $Id: asip-status,v 1.5 2002/02/06 06:13:50 jwa Exp $ # use strict; use IO::Socket; # sucks because Timeout doesn't my ($arg); my ($host); while ($arg = shift @ARGV) { $main::show_icon = 1 if ($arg eq "-i"); $main::debug = 1 if ($arg eq "-d"); $main::hexdump = 1 if ($arg eq "-x"); $host = $arg if ($arg !~ /^-/); } if ($host eq "") { print "usage: $0 hostname [-i show icon] [-d debug] [-x hex dump]\n"; exit(-1); } my ($packet) = build_packet(); my ($code) = sendpacket($host, 548, $packet); exit $code; sub build_packet { my (@packet) = ( 0x00, # 0- request, 1-reply 0x03, # 3- DSIGetStatus 0xde, 0xad, 0x00, # request ID 0x00, 0x00, 0x00, 0x00, # data field 0x00, 0x00, 0x00, 0x00, # length of data stream header 0x00, 0x00, 0x00, 0x00 # reserved ); my ($packet) = pack("C*", @packet); return $packet; } sub sendpacket { my ($host, $port, $packet) = @_; my ($b, $buf); print "opening $host:$port\n" if ($main::debug); my ($asip_sock) = IO::Socket::INET->new( PeerAddr => $host, PeerPort => $port, Proto => 'tcp', Type => SOCK_STREAM, Timeout => 10 ) || die "connect to $host failure: $!"; $asip_sock->autoflush(1); print "sending packet\n" if ($main::debug); my ($count) = syswrite($asip_sock, $packet, length($packet)); if ($count != length($packet)) { print "only wrote $count of " . length($packet) . " bytes?\n"; exit(-1); } # reply can span multiple packets print "sysread: " if ($main::debug); while (sysread($asip_sock, $b, 256)) { $buf .= $b; print "." if ($main::debug); } close ($asip_sock); print " read " . length($buf) . " bytes\n" if ($main::debug); if (length($buf) == 0) { print "empty reply packet?\n"; return -2; } else { print "AFP reply from $host:$port\n"; return (parse_packet($buf)); } } sub parse_packet { my ($buf) = shift @_; my (@packet); my ($i); hexdump($buf) if ($main::hexdump); for ($i=0;$i 0) && ($cmd == 3)) { my (@AFPpacket) = @packet[($edo+16)..($edo+16+$datalen)]; return (parse_FPGetSrvrInfo(@AFPpacket)); } else { print "I don't know how to parse this type of packet.\n"; return(2); } } sub parse_FPGetSrvrInfo() { my (@packet) = @_; my ($i); my ($machinetype_offset) = unpack("n", @packet[0] . @packet[1]); print "Machine type offset in packet: $machinetype_offset\n" if ($main::debug); my ($machinetype) = extract(1, $machinetype_offset, @packet); print "Machine type: $machinetype\n"; my ($afpversioncount_offset) = unpack("n", @packet[2] . @packet[3]); print "AFPversion count offset: $afpversioncount_offset\n" if ($main::debug); my (@afpversions) = extract(0, $afpversioncount_offset, @packet); print "AFP versions: " . join(",", @afpversions) . "\n"; my ($uamcount_offset) = unpack("n", @packet[4] . @packet[5]); print "UAMcount offset: $uamcount_offset\n" if ($main::debug); my (@uams) = extract(0, $uamcount_offset, @packet); print "UAMs: " . join(",", @uams) . "\n"; my ($allow_guest) = 0; $allow_guest = 1 if (grep(/No User Authen/, @uams)); # it would be cute to see the icon. my ($icon_offset) = unpack("n", @packet[6] . @packet[7]); print "Volume Icon & Mask offset: $icon_offset\n" if ($main::debug); my ($flags) = unpack("n", @packet[8] . @packet[9]); my (@flags) = parse_afp_flags($flags); print "Flags: "; print "$flags - " if ($main::debug); print join(",", @flags) . "\n"; # server name starts at offset+10, length byte first. my ($servername_len) = unpack("C1", @packet[10]); my ($servername) = join("", @packet[11..(11+$servername_len-1)]); print "Server name length: $servername_len\n" if ($main::debug); print "Server name: $servername\n"; my ($offset) = 11 + $servername_len; # quietly ++ the $offset to account for the padding that happens # in the reply packet if the field names don't align on an even boundary $offset++ if ($servername_len % 2 == 0); print "New offset: $offset\n" if ($main::debug); my ($signature_offset) = unpack("n2", @packet[$offset] . @packet[$offset+1]); print "Signature offset: $signature_offset\n" if ($main::debug); if ($signature_offset) { my ($signature) = extract(1, $signature_offset, @packet); print "Signature:\n"; hexdump($signature); } my ($network_address_count_offset) = unpack("n2", @packet[$offset+2] . @packet[$offset+3]); print "Network address count offset: $network_address_count_offset\n" if ($main::debug); my (@addrs) = extract(0, $network_address_count_offset, @packet); while (@addrs) { my ($addr) = extract_network_address(shift @addrs); print "Network address: $addr\n"; } draw_icon($icon_offset, @packet) if ($main::show_icon); return $allow_guest; } # getsrvbyname .. sorta .. sub getasipsrv { my ($what, $code) = @_; if ($what eq "flags") { return "Request" if ($code == 0); return "Reply" if ($code == 1); } if ($what eq "command") { return "DSICloseSession" if ($code == 1); return "DSICommand" if ($code == 2); return "DSIGetStatus" if ($code == 3); return "DSIOpenSession" if ($code == 4); return "DSITickle" if ($code == 5); return "DSIWrite" if ($code == 6); return "DSIAttention" if ($code == 7); } return "[$what/$code] unknown"; } # return "counted" data at @packet[$offset] # when called with a zero as the first argument, this will # look in the packet for the count. Otherwise, it will # assume I know what I'm doing. (hah, what a foolish function..) sub extract { my ($count, $offset, @packet) = @_; my ($i, $j); my (@items, $data); my ($hack); if ($count == 0) { ($count) = unpack("C", @packet[$offset]); return if ($count == 0); $offset++; } else { $hack = 1; } #print ">> extracting $count items from offset $offset\n"; for ($i=0;$i<$count;$i++) { #print "Working on count $i\n"; my ($len) = unpack("C1", @packet[$offset]); $data = join("", @packet[$offset+1..$offset+$len]); #print "$i. [$data] ($len)\n"; push (@items, $data); $offset = $offset + $len + 1; #print "new offset is $offset\n"; } return $data if ($hack); return @items; } sub draw_icon { my ($offset, @packet) = @_; my ($cols); my ($i, $j); # icons are 32x32 bitmaps; 128 byte icon + 128 byte mask # to show the mask, change 128 to 256. for ($i=0;$i<128;$i++) { my ($c) = @packet[$i+$offset]; my ($bin) = unpack ("B*", $c); for ($j=0;$j<8;$j++) { if (substr($bin, $j, 1)) { print "#"; } else { print " "; } } $cols++; if ($cols == 4) { $cols = 0; print "\n"; } } print "\n"; } sub parse_afp_flags { my ($flags) = shift @_; my (@flags); # $flags is a 16 bit little-endian number push (@flags, "SupportsCopyFile") if ($flags & 1); push (@flags, "SupportsChgPwd") if ($flags & 2); push (@flags, "DontAllowSavePwd") if ($flags & 4); push (@flags, "SupportsServerMessages") if ($flags & 8); push (@flags, "SupportsServerSignature") if ($flags & 16); push (@flags, "SupportsTCP/IP") if ($flags & 32); push (@flags, "SupportsSrvrNotifications") if ($flags & 64); return @flags; } sub hexdump { my ($buf) = @_; my ($p, $c, $pc, $str); my ($i); for ($i=0;$i 31) && ($c < 127)) { $str .= $p; } else { $str .= "."; } if ($pc == 16) { print " $str\n"; undef $str; $pc = 0; } } print " " x (16 - $pc); print " $str \n"; } sub extract_network_address { my ($nap) = shift @_; my (@nap) = unpack("C*", $nap); # 1st byte is 'tag' # 1 - IP address; 4 bytes # 2 - IP address (4) + port (2) # 3 - DDP (2 bytes net, 1 byte node, 1 byte socket) if (@nap[0] == 1) { # quad my ($ip) = sprintf "%d.%d.%d.%d", @nap[1], @nap[2], @nap[3], @nap[4]; return $ip; } if (@nap[0] == 2) { # quad+port my ($ipport) = sprintf "%d.%d.%d.%d:%d", @nap[1], @nap[2], @nap[3], @nap[4], (@nap[5]*256) + @nap[6]; return $ipport; } if (@nap[0] == 3) { # untested. I have no LocalTalk/EtherTalk hosts.. my ($net_node_socket) = sprintf "%d@%d:%d", (@nap[0] * 256) + @nap[1], @nap[2], @nap[3]; } else { return "(no handler for packet type " . @nap[0]. ")"; } }