cluster: STABLE3 - fence_agents: Replaced telnet_ssl by fence_nss_wrapper

Jan Friesse honzaf@fedoraproject.org
Wed Apr 1 09:48:00 GMT 2009


Gitweb:        http://git.fedorahosted.org/git/cluster.git?p=cluster.git;a=commitdiff;h=a68e594ae0461a82b8364b2a88e0698c0b0cad46
Commit:        a68e594ae0461a82b8364b2a88e0698c0b0cad46
Parent:        451c53d7b8f92e6e35bf871606a5b7286852b9a7
Author:        Jan Friesse <jfriesse@redhat.com>
AuthorDate:    Wed Mar 25 17:00:43 2009 +0100
Committer:     Jan Friesse <jfriesse@redhat.com>
CommitterDate: Wed Apr 1 11:47:50 2009 +0200

fence_agents: Replaced telnet_ssl by fence_nss_wrapper

Fence_nss_wrapper is drop-on replacement of telnet_ssl.
This is hobbit like tool with support for NSS (SSL)
connection.

In contrast of old tool, this has many advantages:
- Support for IPv6 (user can force IPv4)
- Use polling instead of active waiting (non-blocking
socket + sleep)
- It's based on NSS (future of Fedora SSL)
- Support for service names (not used in agents)
---
 fence/agents/lib/Makefile                    |    2 +-
 fence/agents/lib/fencing.py.py               |    2 +-
 fence/agents/lib/telnet_ssl.py               |   72 ----
 fence/agents/nss_wrapper/Makefile            |   24 ++
 fence/agents/nss_wrapper/fence_nss_wrapper.c |  483 ++++++++++++++++++++++++++
 make/fencebuild.mk                           |    1 +
 6 files changed, 510 insertions(+), 74 deletions(-)

diff --git a/fence/agents/lib/Makefile b/fence/agents/lib/Makefile
index 049ff6e..2cccd98 100644
--- a/fence/agents/lib/Makefile
+++ b/fence/agents/lib/Makefile
@@ -1,6 +1,6 @@
 include ../../../make/defines.mk
 
-TARGET= fencing.py telnet_ssl fencing_snmp.py
+TARGET= fencing.py fencing_snmp.py
 
 FENCEAGENTSLIB= $(TARGET)
 
diff --git a/fence/agents/lib/fencing.py.py b/fence/agents/lib/fencing.py.py
index e5473e1..34b0caa 100644
--- a/fence/agents/lib/fencing.py.py
+++ b/fence/agents/lib/fencing.py.py
@@ -30,7 +30,7 @@ EC_STATUS_HMC      = 9
 
 TELNET_PATH = "/usr/bin/telnet"
 SSH_PATH    = "/usr/bin/ssh"
-SSL_PATH    = "@FENCEAGENTSLIBDIR@/telnet_ssl"
+SSL_PATH    = "@SBINDIR@/fence_nss_wrapper"
 
 all_opt = {
 	"help"    : {
diff --git a/fence/agents/lib/telnet_ssl.py b/fence/agents/lib/telnet_ssl.py
deleted file mode 100644
index 5d0c981..0000000
--- a/fence/agents/lib/telnet_ssl.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/python
-
-#####
-## simple telnet client with SSL support 
-##
-## ./telnet_ssl host port
-#####
-
-import sys, socket, string, fcntl, os , time
-from OpenSSL import SSL
-
-#BEGIN_VERSION_GENERATION
-RELEASE_VERSION=""
-REDHAT_COPYRIGHT=""
-BUILD_DATE=""
-#END_VERSION_GENERATION
-
-def main():
-	hostname = None
-	port = None
-
-	if (len(sys.argv) != 3):
-		print "Error: You have to enter hostname and port number\n"
-		sys.exit(-1)
-
-	hostname = sys.argv[1]
-	port = int(sys.argv[2])
-
-	try:
-		s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
-		s.connect((hostname,port))
-		ctx = SSL.Context(SSL.SSLv23_METHOD)
-		conn = SSL.Connection(ctx, s)
-		conn.set_connect_state()
-	except socket.error, e:
-		print "Error: Unable to connect to %s:%s %s" % (hostname, port, str(e))
-		sys.exit(-1)
-
-	fcntl.fcntl(sys.stdin, fcntl.F_SETFL, os.O_NONBLOCK) 
-	s.settimeout(0)
-
-	while 1:
-		try:
-			write_buff = sys.stdin.readline()
-			if (len(write_buff) > 0):
-				write_buff = string.rstrip(write_buff)
-				i = 10
-				while i > 0:
-					i = i-1
-					try:
-						conn.send(write_buff + "\r\n")
-						i = -1
-					except SSL.WantReadError:
-						## We have to wait for connect, mostly just for first time
-						time.sleep(1)
-				if i == 0:
-					sys.exit(-2)
-		except IOError:
-			1
-
-		try:
-			read_buff = conn.recv(4096)
-			print read_buff
-			sys.stdout.flush()
-		except SSL.WantReadError:
-			1
-		except SSL.ZeroReturnError:
-			break
-
-
-if __name__ == "__main__":
-	main()
diff --git a/fence/agents/nss_wrapper/Makefile b/fence/agents/nss_wrapper/Makefile
new file mode 100644
index 0000000..ae74124
--- /dev/null
+++ b/fence/agents/nss_wrapper/Makefile
@@ -0,0 +1,24 @@
+TARGET= fence_nss_wrapper
+
+SBINDIRT=$(TARGET)
+
+all: ${TARGET}
+
+include ../../../make/defines.mk
+include $(OBJDIR)/make/cobj.mk
+include $(OBJDIR)/make/clean.mk
+include $(OBJDIR)/make/install.mk
+include $(OBJDIR)/make/uninstall.mk
+
+OBJS=	fence_nss_wrapper.o
+
+CFLAGS += -I${incdir} -I${nsprincdir} -I${nssincdir}
+
+LDFLAGS += -L${libdir} -L${nsprlibdir} -L${nsslibdir} -lnss3 -lssl3
+
+${TARGET}: ${OBJS}
+	$(CC) -o $@ $^ $(LDFLAGS)
+
+clean: generalclean
+
+-include $(OBJS:.o=.d)
diff --git a/fence/agents/nss_wrapper/fence_nss_wrapper.c b/fence/agents/nss_wrapper/fence_nss_wrapper.c
new file mode 100644
index 0000000..c6b3cda
--- /dev/null
+++ b/fence/agents/nss_wrapper/fence_nss_wrapper.c
@@ -0,0 +1,483 @@
+/** @file fence_nss_wrapper.c - Main source code of hobbit like tool with
+  support for NSS (SSL) connection.
+*/
+#include <stdio.h>
+#include <nss.h>
+#include <ssl.h>
+#include <prio.h>
+#include <prnetdb.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <getopt.h>
+#include <libgen.h>
+
+/*---- CONSTANTS -------------*/
+
+/** Default operation = connect and telnet*/
+#define OPERATION_DEFAULT 0
+/** Operation display help*/
+#define OPERATION_HELP 1
+
+/** Default mode of connection. Try first found working address*/
+#define MODE_DEFAULT 3
+/** Use only IPv4*/
+#define MODE_IP4MODE 1
+/** Use only IPv6*/
+#define MODE_IP6MODE 2
+/** Use RAW mode - no change of \r and \n to \r\n*/
+#define MODE_RAW     4
+/** Use non-secure mode (without SSL, only pure socket)*/
+#define MODE_NO_SSL  8
+
+/*------ Functions ---------------*/
+
+/** Return port inserted in string. Fuction tests, if port is integer, and than return
+  integer value of string. Otherwise, it will use /etc/services.  On fail, it returns
+  port -1.
+  @param port_s Input port or service name
+  @return port number (converted with ntohs) on success, otherwise -1.
+*/
+int return_port(char *port_s) {
+  char *end_c;
+  int res;
+  struct servent *serv;
+
+  res=strtol(port_s,&end_c,10);
+
+  if (*end_c=='\0') return res;
+
+  /*It's not number, so try service name*/
+  serv=getservbyname(port_s,NULL);
+
+  if (serv==NULL) return -1;
+
+  return ntohs(serv->s_port);
+}
+
+/** Hook handler for bad certificate (because we have no DB, EVERY certificate is bad).
+  Returned value is always SECSuccess = it's ok certificate.
+  @param arg NULL value
+  @param fd socket cased error
+  @return SECSuccess.
+*/
+SECStatus nss_bad_cert_hook(void *arg,PRFileDesc *fd) {
+  return SECSuccess;
+}
+
+/** Display last NSPR/NSS error code and user readable message.
+*/
+void print_nspr_error(void) {
+  fprintf(stderr,"Error (%d): %s\n",PR_GetError(),PR_ErrorToString(PR_GetError(),PR_LANGUAGE_I_DEFAULT));
+}
+
+/** Initialize NSS. NSS is initialized without DB and with
+  domnestic policy.
+  @return 1 on success, otherwise 0.
+*/
+int init_nss(void) {
+  if ((NSS_NoDB_Init(NULL)!=SECSuccess) ||
+      (NSS_SetDomesticPolicy()!=SECSuccess)) {
+    print_nspr_error();
+
+    return 0;
+  }
+
+  SSL_ClearSessionCache();
+
+  return 1;
+}
+
+/** Create socket. If ssl is >0, socket is ssl enabled.
+  @param ssl Enable ssl (Client, SSL2+3, no TLS, compatible hello) if PR_TRUE, otherwise no.
+  @param ipv6 New socket will be IPv4 if this value is 0, otherwise it will be ipv6
+  @return NULL on error, otherwise socket.
+*/
+PRFileDesc *create_socket(int ssl,int ipv6) {
+  PRFileDesc *res_socket;
+
+  res_socket=PR_OpenTCPSocket((ipv6?PR_AF_INET6:PR_AF_INET));
+  if (res_socket==NULL) {
+    print_nspr_error();
+
+    return NULL;
+  }
+
+  if (!ssl) return res_socket;
+
+  if (!(res_socket=SSL_ImportFD(NULL,res_socket))) {
+    print_nspr_error();
+
+    return NULL;
+  }
+
+  if ((SSL_OptionSet(res_socket,SSL_SECURITY,ssl)!=SECSuccess) ||
+      (SSL_OptionSet(res_socket,SSL_HANDSHAKE_AS_SERVER,PR_FALSE)!=SECSuccess) ||
+      (SSL_OptionSet(res_socket,SSL_HANDSHAKE_AS_CLIENT,PR_TRUE)!=SECSuccess) ||
+      (SSL_OptionSet(res_socket,SSL_ENABLE_SSL2,ssl)!=SECSuccess) ||
+      (SSL_OptionSet(res_socket,SSL_ENABLE_SSL3,ssl)!=SECSuccess) ||
+      (SSL_OptionSet(res_socket,SSL_ENABLE_TLS,PR_FALSE)!=SECSuccess) ||
+      (SSL_OptionSet(res_socket,SSL_V2_COMPATIBLE_HELLO,ssl)!=SECSuccess) ||
+      (SSL_SetPKCS11PinArg(res_socket,NULL)==-1) ||
+      (SSL_AuthCertificateHook(res_socket,SSL_AuthCertificate,CERT_GetDefaultCertDB())!=SECSuccess) ||
+      (SSL_BadCertHook(res_socket,nss_bad_cert_hook,NULL)!=SECSuccess)) {
+    print_nspr_error();
+
+    if (PR_Close(res_socket)!=PR_SUCCESS) {
+      print_nspr_error();
+    }
+
+    return NULL;
+  }
+
+  return res_socket;
+}
+
+/** Create socket and connect to it.
+  @param hostname Hostname to connect
+  @param port Port name/number to connect
+  @param mode Connection mode. Bit-array of MODE_NO_SSL, MODE_IP6MODE, MODE_IP4MODE.
+  @return NULL on error, otherwise connected socket.
+*/
+PRFileDesc *create_connected_socket(char *hostname,int port,int mode) {
+  PRAddrInfo *addr_info;
+  void *addr_iter;
+  PRNetAddr addr;
+  PRFileDesc *socket;
+  int can_exit,valid_socket;
+  PRUint16 af_spec;
+
+  socket=NULL;
+
+  addr_info=NULL;
+
+  af_spec=PR_AF_UNSPEC;
+
+  if (!(mode&MODE_IP6MODE)) af_spec=PR_AF_INET;
+
+  addr_info=PR_GetAddrInfoByName(hostname,af_spec,PR_AI_ADDRCONFIG);
+
+  if (addr_info == NULL) {
+    print_nspr_error();
+    return NULL;
+  }
+
+  /*We have socket -> enumerate and try to connect*/
+  addr_iter=NULL;
+  can_exit=0;
+  valid_socket=0;
+
+  while (!can_exit) {
+    addr_iter=PR_EnumerateAddrInfo(addr_iter,addr_info,port,&addr);
+
+    if (addr_iter==NULL) {
+      can_exit=1;
+    } else {
+      if ((PR_NetAddrFamily(&addr)==PR_AF_INET && (mode&MODE_IP4MODE)) ||
+          (PR_NetAddrFamily(&addr)==PR_AF_INET6 && (mode&MODE_IP6MODE))) {
+        /*Type of address is what user want, try to create socket and make connection*/
+
+        /*Create socket*/
+        socket=create_socket(!(mode&MODE_NO_SSL),(PR_NetAddrFamily(&addr)==PR_AF_INET6));
+
+        if (socket) {
+          /*Try to connect*/
+          if (PR_Connect(socket,&addr,PR_INTERVAL_NO_TIMEOUT)==PR_SUCCESS) {
+            /*Force handshake*/
+            if ((!(mode&MODE_NO_SSL)) && SSL_ForceHandshake(socket)!=SECSuccess) {
+              /*Handhake failure -> fail*/
+              print_nspr_error();
+              if (PR_Close(socket)!=PR_SUCCESS) {
+                print_nspr_error();
+                can_exit=1;
+              }
+              socket=NULL;
+            }
+
+            /*Socket is connected -> we can return it*/
+            can_exit=1;
+          } else {
+            /*Try another address*/
+            if (PR_Close(socket)!=PR_SUCCESS) {
+              print_nspr_error();
+              can_exit=1;
+            }
+            socket=NULL;
+          }
+        }
+      }
+    }
+  }
+
+  if (!socket) {
+    /*Socket is unvalid -> we don't found any usable address*/
+    fprintf(stderr,"Can't connect to host %s on port %d!\n",hostname,port);
+  }
+
+  PR_FreeAddrInfo(addr_info);
+
+  return socket;
+}
+
+/** Parse arguments from command line.
+  @param argc Number of arguments in argv
+  @param argv Array of arguments
+  @param mode Pointer to int will be filled with OPERATION_DEFAULT or OPERATION_HELP.
+  @param mode Pointer to int will be filled with MODE_DEFAULT, MODE_IP4MODE or MODE_IP4MODE.
+  @return 1 on success, otherwise 0.
+*/
+int parse_cli(int argc,char *argv[],int *operation,int *mode,char **hostname,char **port) {
+  int opt;
+
+  *operation=OPERATION_DEFAULT;
+  *mode=MODE_DEFAULT;
+  *port=NULL;
+  *hostname=NULL;
+
+  while ((opt=getopt(argc,argv,"h46rz"))!=-1) {
+    switch (opt) {
+      case 'h':
+        *operation=OPERATION_HELP;
+
+        return 0;
+      break;
+
+      case '4':
+        (*mode)&=~MODE_IP6MODE;
+        (*mode)|=MODE_IP4MODE;
+      break;
+
+      case '6':
+        (*mode)&=~MODE_IP4MODE;
+        (*mode)|=MODE_IP6MODE;
+      break;
+
+      case 'r':
+        (*mode)|=MODE_RAW;
+      break;
+
+      case 'z':
+        (*mode)|=MODE_NO_SSL;
+      break;
+
+      default:
+        return 0;
+      break;
+    }
+  }
+
+  if (argc-optind<2) {
+    fprintf(stderr,"Hostname and port is expected!\n");
+
+    return 0;
+  }
+
+  *hostname=argv[optind];
+  *port=argv[optind+1];
+
+  return 1;
+}
+
+/** Show usage of application.
+  @param pname Name of program (usually basename of argv[0])
+*/
+void show_usage(char *pname) {
+  printf("usage: %s [options] hostname port\n", pname);
+  printf("   -4             Force to use IPv4\n");
+  printf("   -6             Force to use IPv6\n");
+  printf("   -r             Use RAW connection (don't convert \\r and \\n characters)\n");
+  printf("   -z             Don't use SSL connection (use pure socket)\n");
+  printf("   -h             Show this help\n");
+}
+
+/** Convert End Of Lines (Unix \n, Macs \r or DOS/Win \r\n) to \r\n.
+  @param in_buffer Input buffer
+  @param in_size Input buffer size
+  @param out_buffer Output buffer (must be prealocated). Should be (2*in_size) (in worst case)
+  @param out_size There will be size of out_buffer
+  @param in_state Internal state of finite automata. First call should have this 0, other calls
+    shouldn't change this value. After end of file, you may add to this value +100 and call this
+    again, to make sure of proper end (in_buffer can be in this case everything, including NULL).
+*/
+void convert_eols(char *in_buffer,int in_size,char *out_buffer,int *out_size,int *in_state) {
+  int in_pos,out_pos;
+  int status;
+  char in_char;
+
+  out_pos=0;
+  status=*in_state;
+
+  if (status==100 || status==101) {
+    if (status==101) {
+      out_buffer[out_pos++]='\r';
+      out_buffer[out_pos++]='\n';
+    }
+  } else {
+    for (in_pos=0;in_pos<in_size;in_pos++) {
+      in_char=in_buffer[in_pos];
+
+      switch (status) {
+        case 0:
+          if (in_char=='\r') status=1;
+          if (in_char=='\n') {
+            out_buffer[out_pos++]='\r';
+            out_buffer[out_pos++]='\n';
+          }
+          if ((in_char!='\r') && (in_char!='\n')) out_buffer[out_pos++]=in_char;
+        break;
+
+        case 1:
+          out_buffer[out_pos++]='\r';
+          out_buffer[out_pos++]='\n';
+
+          if (in_char!='\n') out_buffer[out_pos++]=in_char;
+
+          status=0;
+        break;
+      }
+    }
+  }
+
+  *out_size=out_pos;
+  *in_state=status;
+}
+
+/** Start polling cycle.
+  @param socket Network connected socket.
+  @param mode Bit-array of MODE_*. This function take care on MODE_RAW.
+  @return 0 on failure, otherwise 1
+*/
+int poll_cycle(PRFileDesc *socket,int mode) {
+  PRPollDesc pool[2];
+  char buffer[1024],buffer_eol[1024*2];
+  int readed_bytes;
+  int can_exit;
+  int res;
+  int bytes_to_write;
+  int eol_state;
+
+  can_exit=0;
+  eol_state=0;
+
+  /* Fill pool*/
+  pool[1].fd=socket;
+  pool[0].fd=PR_STDIN;
+  pool[0].in_flags=pool[1].in_flags=PR_POLL_READ;
+  pool[0].out_flags=pool[1].out_flags=0;
+
+  while (!can_exit) {
+    res=(PR_Poll(pool,sizeof(pool)/sizeof(PRPollDesc),PR_INTERVAL_NO_TIMEOUT));
+
+    if (res==-1) {
+      print_nspr_error();
+
+      return 0;
+    }
+
+    if (pool[1].out_flags==PR_POLL_READ) {
+      /*We have something in socket*/
+      if ((readed_bytes=PR_Read(pool[1].fd,buffer,sizeof(buffer)))>0) {
+        if (PR_Write(PR_STDOUT,buffer,readed_bytes)!=readed_bytes) {
+          print_nspr_error();
+
+          return 0;
+        }
+      } else {
+        /*End of stream -> quit*/
+        can_exit=1;
+      }
+    }
+
+    if (pool[0].out_flags==PR_POLL_READ) {
+      /*We have something in stdin*/
+      if ((readed_bytes=PR_Read(pool[0].fd,buffer,sizeof(buffer)))>0) {
+
+        if (!(mode&MODE_RAW)) {
+          convert_eols(buffer,readed_bytes,buffer_eol,&bytes_to_write,&eol_state);
+        } else
+          bytes_to_write=readed_bytes;
+
+        if (PR_Write(pool[1].fd,(mode&MODE_RAW?buffer:buffer_eol),bytes_to_write)!=bytes_to_write) {
+          print_nspr_error();
+
+          return 0;
+        }
+      } else {
+        /*End of stream -> quit*/
+        if (!(mode&MODE_RAW)) {
+          convert_eols(NULL,0,buffer_eol,&bytes_to_write,&eol_state);
+          if (PR_Write(pool[1].fd,buffer_eol,bytes_to_write)!=bytes_to_write) {
+            print_nspr_error();
+
+            return 0;
+          }
+        }
+
+        can_exit=1;
+      }
+    }
+
+    pool[0].out_flags=pool[1].out_flags=0;
+  } /*while (!can_exit)*/
+
+  return 1;
+}
+
+void atexit_handler(void) {
+  if (PR_Initialized())
+    PR_Cleanup();
+
+  if (fclose(stdout)!=0) {
+    fprintf(stderr,"Can't close stdout!\n");
+
+    exit(1);
+  }
+}
+
+/** Entry point of application.
+  @param argc Number of arguments on command line
+  @param argv Array of strings with arguments from command line
+  @return 0 on success, otherwise >0.
+*/
+int main(int argc,char *argv[]) {
+  int mode,operation;
+  char *hostname, *port;
+  char *pname;
+  int port_n;
+  PRFileDesc *fd_socket;
+  int res;
+
+  pname=basename(argv[0]);
+
+  atexit(atexit_handler);
+
+  if (!parse_cli(argc,argv,&operation,&mode,&hostname,&port) || operation==OPERATION_HELP) {
+    show_usage(pname);
+
+    if (operation!=OPERATION_HELP) return 1;
+
+    return 0;
+  }
+
+  if ((port_n=return_port(port))==-1) {
+    fprintf(stderr,"Error. Unknown port number/name %s!\n",port);
+
+    return 1;
+  }
+
+  if (!(mode&MODE_NO_SSL)) {
+    if (!init_nss()) return 1;
+  }
+
+  if (!(fd_socket=create_connected_socket(hostname,port_n,mode)))
+    return 1;
+
+  res=poll_cycle(fd_socket,mode);
+
+  if (PR_Close(fd_socket)!=PR_SUCCESS) {
+    print_nspr_error();
+
+    return 1;
+  }
+
+  return (res?0:1);
+}
diff --git a/make/fencebuild.mk b/make/fencebuild.mk
index 7c5f0c5..48145b7 100644
--- a/make/fencebuild.mk
+++ b/make/fencebuild.mk
@@ -19,6 +19,7 @@ $(TARGET):
 		-e 's#@FENCEAGENTSLIBDIR@#${fenceagentslibdir}#g' \
 		-e 's#@SNMPBIN@#${snmpbin}#g' \
 		-e 's#@LOGDIR@#${logdir}#g' \
+		-e 's#@SBINDIR@#${sbindir}#g' \
 	> $@
 
 clean: generalclean



More information about the Cluster-cvs mailing list