IRC.pm 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package Cpanel::iContact::Provider::IRC;
  2. use strict;
  3. use warnings;
  4. use parent 'Cpanel::iContact::Provider';
  5. sub send {
  6. my ($self) = @_;
  7. my @missing = grep { !defined $self->{'contact'}{$_} } qw{IRCSERVER};
  8. die "Kit not complete! Missing: " . join( ", ", @missing ) if scalar( @missing );
  9. my $args_hr = $self->{'args'};
  10. my @errs;
  11. my $subject = $args_hr->{'subject'};
  12. my $body = ${ $args_hr->{'text_body'} };
  13. local $@;
  14. eval {
  15. my $response;
  16. $self->_send(
  17. 'destination' => $args_hr->{'to'}[0],
  18. 'subject' => $subject,
  19. 'content' => $body
  20. );
  21. };
  22. push( @errs, $@ ) if $@;
  23. if (@errs) {
  24. die "One or more notification attempts failed. Details below:\n"
  25. . join( "\n", @errs );
  26. }
  27. return 1;
  28. }
  29. my $conn;
  30. sub _send {
  31. my ( $self, %args ) = @_;
  32. if( $ENV{'AUTHOR_TESTS'} ) {
  33. my $debugmsg = "# Attempting connection to $self->{'contact'}{'IRCSERVER'}:$self->{'contact'}{'IRCPORT'} as $self->{'contact'}{'IRCNICK'} in channel $args{'destination'}";
  34. $debugmsg .= " using SSL" if $self->{'contact'}{'IRCUSESSL'};
  35. print $debugmsg, "\n";
  36. }
  37. my @message_lines = _format_message_for_irc( $args{'subject'}, $args{'content'}, $args{'destination'} );
  38. require IO::Socket::INET;
  39. require IO::Socket::SSL;
  40. require Time::HiRes;
  41. # Don't laugh, some of these notices are so long (and the server so laggy at printing) that this actually is reasonable.
  42. # Usually messages are delayed as a flood limiting action.
  43. local $SIG{'ALRM'} = sub { die "Timed out waiting for notification to post to IRC channel!" };
  44. alarm(60);
  45. $conn = IO::Socket::INET->new("$self->{'contact'}{'IRCSERVER'}:$self->{'contact'}{'IRCPORT'}", ) or die $!;
  46. binmode( $conn, ":utf8" );
  47. if( $self->{'contact'}{'IRCUSESSL'} ) {
  48. print "# Upgrading connection to use SSL...\n" if $ENV{'AUTHOR_TESTS'};
  49. IO::Socket::SSL->start_SSL( $conn, 'SSL_HOSTNAME' => $self->{'contact'}{'IRCSERVER'}, 'SSL_verify_mode' => 0 ) or die $IO::Socket::SSL::ERROR;
  50. }
  51. print "# [SENT] NICK $self->{'contact'}{'IRCNICK'}\r\n" if $ENV{'AUTHOR_TESTS'};
  52. print $conn "NICK $self->{'contact'}{'IRCNICK'}\r\n";
  53. print "# [SENT] USER cpsaurus * 8 :cPanel & WHM Notification Bot v0.1 (github.com/troglodyne/iContact-cPanel-Plugins)\r\n" if $ENV{'AUTHOR_TESTS'};
  54. print $conn "USER cpsaurus * 8 :cPanel & WHM Notification Bot v0.1 (github.com/troglodyne/iContact-cPanel-Plugins)\r\n";
  55. my %got;
  56. while( $conn ) {
  57. last if !scalar(@message_lines);
  58. # Print it all then leave like a bad smell
  59. if( $got{'366'} && $got{'332'} ) {
  60. foreach my $shake_line ( @message_lines ) {
  61. print "# [SENT] $shake_line" if $ENV{'AUTHOR_TESTS'};
  62. print $conn $shake_line;
  63. }
  64. last;
  65. }
  66. my $line= readline( $conn ) || "";
  67. #$line =~ s/^[^[:print:]]+$//; # Collapse blank lines
  68. next if !$line;
  69. print "# [GOT][" . length($line) . "] $line" if $ENV{'AUTHOR_TESTS'};
  70. my @msgparts = split( ' ', $line );
  71. $msgparts[1] ||= '';
  72. # PING handler
  73. if( $msgparts[0] eq 'PING' ) {
  74. print "# [SENT] PONG $msgparts[1]\r\n" if $ENV{'AUTHOR_TESTS'};
  75. print $conn "PONG $msgparts[1]\r\n";
  76. next;
  77. }
  78. # MOTD/JOIN handler
  79. if( grep { $_ eq $msgparts[1] } qw{376 422} ) {
  80. print "# [SENT] JOIN $args{'destination'}\r\n" if $ENV{'AUTHOR_TESTS'};
  81. print $conn "JOIN $args{'destination'}\r\n";
  82. next;
  83. }
  84. # Channel join handler, gotta wait for NAMES and TOPIC
  85. if( grep { $_ eq $msgparts[1] } qw{366 332} ) {
  86. print "# [INFO] Noticed we got $msgparts[1] above. Noting that so we can know when to start spamming messages.\n" if $ENV{'AUTHOR_TESTS'};
  87. $got{"$msgparts[1]"} = 1 ;
  88. next;
  89. }
  90. }
  91. print "# [SENT] QUIT :Done sending notification\r\n" if $ENV{'AUTHOR_TESTS'};
  92. print $conn "QUIT :Done sending notification\r\n";
  93. # The connection won't properly un-block until you read the response from QUIT.
  94. # Unfortunately, just setting it to non-block leads to your messages not being processed.
  95. # As such, just read here and let it do it's thing even though you don't actually need the data.
  96. readline( $conn );
  97. $conn->shutdown(2);
  98. return;
  99. }
  100. # https://tools.ietf.org/html/rfc2812#section-2.3
  101. sub _format_message_for_irc {
  102. my ( $subj, $body, $chan ) = @_;
  103. my @msg_lines;
  104. my $prefix = "NOTICE $chan :";
  105. my $suffix = "\r\n"; # 2 chars
  106. my $msglen = 510 - length $prefix; # 512 chars total
  107. # Subject is one line
  108. while( $subj ) {
  109. if( length $subj <= 510 ) {
  110. push( @msg_lines, $prefix . $subj . $suffix );
  111. undef $subj;
  112. } else {
  113. push( @msg_lines, $prefix . substr( $subj, 0, 510, "" ) . $suffix );
  114. }
  115. }
  116. # Body is multiline
  117. my @body_lines = split( "\n", $body );
  118. foreach my $line (@body_lines) {
  119. while( $line ) {
  120. if( length $line <= 510 ) {
  121. push( @msg_lines, $prefix . $line . $suffix );
  122. undef $line;
  123. } else {
  124. push( @msg_lines, $prefix . substr( $line, 0, 510, "" ) . $suffix );
  125. }
  126. }
  127. }
  128. return @msg_lines;
  129. }
  130. sub DESTROY {
  131. $conn->shutdown(2) if $conn;
  132. }
  133. 1;