X-Git-Url: http://git.alex.org.uk diff --git a/nbd-server.c b/nbd-server.c index 70db6d1..f07a29f 100644 --- a/nbd-server.c +++ b/nbd-server.c @@ -159,6 +159,13 @@ char pidfname[256]; /**< name of our PID file */ char pidftemplate[256]; /**< template to be used for the filename of the PID file */ char default_authname[] = SYSCONFDIR "/nbd-server/allow"; /**< default name of allow file */ +int modernsock=0; /**< Socket for the modern handler. Not used + if a client was only specified on the + command line; only port used if + oldstyle is set to false (and then the + command-line client isn't used, gna gna) */ +char* modern_listen; /**< listenaddr value for modernsock */ + /** * Types of virtuatlization **/ @@ -190,6 +197,7 @@ typedef struct { but before starting to serve */ gchar* postrun; /**< command that will be ran after the client disconnects */ + gchar* servename; /**< name of the export as selected by nbd-client */ } SERVER; /** @@ -215,6 +223,7 @@ typedef struct { make -m and -c mutually exclusive */ u32 difffilelen; /**< number of pages in difffile */ u32 *difmap; /**< see comment on the global difmap for this one */ + gboolean modern; /**< client was negotiated using modern negotiation protocol */ } CLIENT; /** @@ -525,8 +534,10 @@ typedef enum { CFILE_VALUE_INVALID, /**< A value is syntactically invalid */ CFILE_VALUE_UNSUPPORTED,/**< A value is not supported in this build */ CFILE_PROGERR, /**< Programmer error */ - CFILE_NO_EXPORTS /**< A config file was specified that does not + CFILE_NO_EXPORTS, /**< A config file was specified that does not define any exports */ + CFILE_INCORRECT_PORT, /**< The reserved port was specified for an + old-style export. */ } CFILE_ERRORS; /** @@ -593,8 +604,7 @@ SERVER* dup_serve(SERVER *s) { * @param a server array * @return 0 success, -1 error */ -int append_serve(SERVER *s, GArray *a) -{ +int append_serve(SERVER *s, GArray *a) { SERVER *ns = NULL; struct addrinfo hints; struct addrinfo *ai = NULL; @@ -686,9 +696,12 @@ GArray* parse_cfile(gchar* f, GError** e) { { "listenaddr", FALSE, PARAM_STRING, NULL, 0 }, }; const int lp_size=sizeof(lp)/sizeof(PARAM); + int do_oldstyle; PARAM gp[] = { { "user", FALSE, PARAM_STRING, &runuser, 0 }, { "group", FALSE, PARAM_STRING, &rungroup, 0 }, + { "oldstyle", FALSE, PARAM_BOOL, &do_oldstyle, 1 }, + { "listenaddr", FALSE, PARAM_STRING, &modern_listen, 0 }, }; PARAM* p=gp; int p_size=sizeof(gp)/sizeof(PARAM); @@ -769,6 +782,11 @@ GArray* parse_cfile(gchar* f, GError** e) { } break; } + if(!strcmp(p[j].paramname, "port") && !strcmp(p[j].target, NBD_DEFAULT_PORT)) { + g_set_error(e, errdomain, CFILE_INCORRECT_PORT, "Config file specifies default port for oldstyle export"); + g_key_file_free(cfile); + return NULL; + } if(err) { if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) { if(!p[j].required) { @@ -818,8 +836,13 @@ GArray* parse_cfile(gchar* f, GError** e) { /* Don't append values for the [generic] group */ if(i>0) { s.socket_family = AF_UNSPEC; + s.servename = groups[i]; append_serve(&s, retval); + } else { + if(!do_oldstyle) { + lp[1].required = 0; + } } #ifndef WITH_SDP if(s.flags & F_SDP) { @@ -1190,19 +1213,69 @@ int expwrite(off_t a, char *buf, size_t len, CLIENT *client) { * * @param client The client we're negotiating with. **/ -void negotiate(CLIENT *client) { +CLIENT* negotiate(int net, CLIENT *client, GArray* servers) { char zeros[128]; - u64 size_host; - u32 flags = NBD_FLAG_HAS_FLAGS; + uint64_t size_host; + uint32_t flags = NBD_FLAG_HAS_FLAGS; + uint16_t smallflags = 0; memset(zeros, '\0', sizeof(zeros)); - if (write(client->net, INIT_PASSWD, 8) < 0) - err("Negotiation failed: %m"); - cliserv_magic = htonll(cliserv_magic); - if (write(client->net, &cliserv_magic, sizeof(cliserv_magic)) < 0) - err("Negotiation failed: %m"); + if(!client || !client->modern) { + if (write(net, INIT_PASSWD, 8) < 0) { + err_nonfatal("Negotiation failed: %m"); + if(client) + exit(EXIT_FAILURE); + } + cliserv_magic = htonll(cliserv_magic); + if (write(net, &cliserv_magic, sizeof(cliserv_magic)) < 0) { + err_nonfatal("Negotiation failed: %m"); + if(client) + exit(EXIT_FAILURE); + } + } + if(!client) { + uint64_t reserved; + uint64_t magic; + uint32_t opt; + uint64_t namelen; + char* name; + int i; + + if(!servers) + err("programmer error"); + write(net, &smallflags, sizeof(uint16_t)); + read(net, &reserved, sizeof(reserved)); + read(net, &magic, sizeof(magic)); + magic = ntohll(magic); + if(magic != cliserv_magic) { + close(net); + return NULL; + } + read(net, &opt, sizeof(opt)); + opt = ntohl(opt); + if(opt != NBD_OPT_EXPORT_NAME) { + close(net); + return NULL; + } + read(net, &namelen, sizeof(namelen)); + namelen = ntohll(namelen); + name = malloc(namelen+1); + name[namelen+1]=0; + read(net, &name, namelen); + for(i=0; ilen; i++) { + SERVER* serve = &(g_array_index(servers, SERVER, i)); + if(!strcmp(serve->servename, name)) { + CLIENT* client = g_new0(CLIENT, 1); + client->server = serve; + client->exportsize = OFFT_MAX; + client->net = net; + client->modern = TRUE; + return client; + } + } + } size_host = htonll((u64)(client->exportsize)); - if (write(client->net, &size_host, 8) < 0) + if (write(net, &size_host, 8) < 0) err("Negotiation failed: %m"); if (client->server->flags & F_READONLY) flags |= NBD_FLAG_READ_ONLY; @@ -1211,6 +1284,7 @@ void negotiate(CLIENT *client) { err("Negotiation failed: %m"); if (write(client->net, zeros, 124) < 0) err("Negotiation failed: %m"); + return NULL; } /** sending macro. */ @@ -1233,7 +1307,7 @@ int mainloop(CLIENT *client) { #ifdef DODBG int i = 0; #endif - negotiate(client); + negotiate(client->net, client, NULL); DEBUG("Entering request loop!\n"); reply.magic = htonl(NBD_REPLY_MAGIC); reply.error = 0; @@ -1569,7 +1643,6 @@ void destroy_pid_t(gpointer data) { int serveloop(GArray* servers) { struct sockaddr_storage addrin; socklen_t addrinlen=sizeof(addrin); - SERVER *serve; int i; int max; int sock; @@ -1590,88 +1663,125 @@ int serveloop(GArray* servers) { FD_SET(sock, &mset); max=sock>max?sock:max; } + if(modernsock) { + FD_SET(modernsock, &mset); + max=modernsock>max?sock:max; + } for(;;) { - CLIENT *client; - int net; + CLIENT *client = NULL; pid_t *pid; memcpy(&rset, &mset, sizeof(fd_set)); if(select(max+1, &rset, NULL, NULL, NULL)>0) { + int net = 0; + SERVER* serve; + DEBUG("accept, "); - for(i=0;ilen;i++) { + if(FD_ISSET(modernsock, &rset)) { + if((net=accept(modernsock, (struct sockaddr *) &addrin, &addrinlen)) < 0) + err("accept: %m"); + client = negotiate(net, NULL, servers); + if(!client) { + err_nonfatal("negotiation failed"); + close(net); + } + } + for(i=0;ilen && !net;i++) { serve=&(g_array_index(servers, SERVER, i)); if(FD_ISSET(serve->socket, &rset)) { - int sock_flags; if ((net=accept(serve->socket, (struct sockaddr *) &addrin, &addrinlen)) < 0) err("accept: %m"); + } + } + if(net) { + int sock_flags; - if((sock_flags = fcntl(net, F_GETFL, 0))==-1) { - err("fcntl F_GETFL"); - } - if(fcntl(net, F_SETFL, sock_flags &~O_NONBLOCK)==-1) { - err("fcntl F_SETFL ~O_NONBLOCK"); - } - client = g_malloc(sizeof(CLIENT)); + if((sock_flags = fcntl(net, F_GETFL, 0))==-1) { + err("fcntl F_GETFL"); + } + if(fcntl(net, F_SETFL, sock_flags &~O_NONBLOCK)==-1) { + err("fcntl F_SETFL ~O_NONBLOCK"); + } + if(!client) { + client = g_new0(CLIENT, 1); client->server=serve; client->exportsize=OFFT_MAX; client->net=net; - set_peername(net, client); - if (!authorized_client(client)) { - msg2(LOG_INFO,"Unauthorized client") ; - close(net); - continue; - } - msg2(LOG_INFO,"Authorized client") ; - pid=g_malloc(sizeof(pid_t)); + } + set_peername(net, client); + if (!authorized_client(client)) { + msg2(LOG_INFO,"Unauthorized client") ; + close(net); + continue; + } + msg2(LOG_INFO,"Authorized client") ; + pid=g_malloc(sizeof(pid_t)); #ifndef NOFORK - if ((*pid=fork())<0) { - msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ; - close(net); - continue; - } - if (*pid>0) { /* parent */ - close(net); - g_hash_table_insert(children, pid, pid); - continue; - } - /* child */ - g_hash_table_destroy(children); - for(i=0;ilen;i++) { - serve=&g_array_index(servers, SERVER, i); - close(serve->socket); - } - /* FALSE does not free the - actual data. This is required, - because the client has a - direct reference into that - data, and otherwise we get a - segfault... */ - g_array_free(servers, FALSE); -#endif // NOFORK - msg2(LOG_INFO,"Starting to serve"); - serveconnection(client); - exit(EXIT_SUCCESS); + if ((*pid=fork())<0) { + msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ; + close(net); + continue; } + if (*pid>0) { /* parent */ + close(net); + g_hash_table_insert(children, pid, pid); + continue; + } + /* child */ + g_hash_table_destroy(children); + for(i=0;ilen;i++) { + serve=&g_array_index(servers, SERVER, i); + close(serve->socket); + } + /* FALSE does not free the + actual data. This is required, + because the client has a + direct reference into that + data, and otherwise we get a + segfault... */ + g_array_free(servers, FALSE); +#endif // NOFORK + msg2(LOG_INFO,"Starting to serve"); + serveconnection(client); + exit(EXIT_SUCCESS); } } } } +void dosockopts(int socket) { +#ifndef sun + int yes=1; +#else + char yes='1'; +#endif /* sun */ + int sock_flags; + + /* lose the pesky "Address already in use" error message */ + if (setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) { + err("setsockopt SO_REUSEADDR"); + } + if (setsockopt(socket,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) { + err("setsockopt SO_KEEPALIVE"); + } + + /* make the listening socket non-blocking */ + if ((sock_flags = fcntl(socket, F_GETFL, 0)) == -1) { + err("fcntl F_GETFL"); + } + if (fcntl(socket, F_SETFL, sock_flags | O_NONBLOCK) == -1) { + err("fcntl F_SETFL O_NONBLOCK"); + } +} + /** * Connect a server's socket. * * @param serve the server we want to connect. **/ -void setup_serve(SERVER *serve) { +int setup_serve(SERVER *serve) { struct addrinfo hints; struct addrinfo *ai = NULL; - struct sigaction sa; - int sock_flags; -#ifndef sun - int yes=1; -#else - char yes='1'; -#endif /* sun */ gchar *port = NULL; int e; @@ -1682,7 +1792,7 @@ void setup_serve(SERVER *serve) { port = g_strdup_printf ("%d", serve->port); if (port == NULL) - return; + return 0; e = getaddrinfo(serve->listenaddr,port,&hints,&ai); @@ -1709,21 +1819,7 @@ void setup_serve(SERVER *serve) { if ((serve->socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) err("socket: %m"); - /* lose the pesky "Address already in use" error message */ - if (setsockopt(serve->socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) { - err("setsockopt SO_REUSEADDR"); - } - if (setsockopt(serve->socket,SOL_SOCKET,SO_KEEPALIVE,&yes,sizeof(int)) == -1) { - err("setsockopt SO_KEEPALIVE"); - } - - /* make the listening socket non-blocking */ - if ((sock_flags = fcntl(serve->socket, F_GETFL, 0)) == -1) { - err("fcntl F_GETFL"); - } - if (fcntl(serve->socket, F_SETFL, sock_flags | O_NONBLOCK) == -1) { - err("fcntl F_SETFL O_NONBLOCK"); - } + dosockopts(serve->socket); DEBUG("Waiting for connections... bind, "); e = bind(serve->socket, ai->ai_addr, ai->ai_addrlen); @@ -1734,17 +1830,43 @@ void setup_serve(SERVER *serve) { err("listen: %m"); freeaddrinfo (ai); + if(serve->servename) { + return 1; + } else { + return 0; + } +} - sa.sa_handler = sigchld_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - if(sigaction(SIGCHLD, &sa, NULL) == -1) - err("sigaction: %m"); - sa.sa_handler = sigterm_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - if(sigaction(SIGTERM, &sa, NULL) == -1) - err("sigaction: %m"); +void open_modern(void) { + struct addrinfo hints; + struct addrinfo* ai = NULL; + struct sock_flags; + int e; + + memset(&hints, '\0', sizeof(hints)); + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + e = getaddrinfo(modern_listen, NBD_DEFAULT_PORT, &hints, &ai); + if(e != 0) { + fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(e)); + exit(EXIT_FAILURE); + } + if((modernsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol))<0) { + err("socket: %m"); + } + + dosockopts(modernsock); + + if(bind(modernsock, ai->ai_addr, ai->ai_addrlen)) { + err("bind: %m"); + } + if(listen(modernsock, 10) <0) { + err("listen: %m"); + } + + freeaddrinfo(ai); } /** @@ -1752,11 +1874,27 @@ void setup_serve(SERVER *serve) { **/ void setup_servers(GArray* servers) { int i; + struct sigaction sa; + int want_modern=0; for(i=0;ilen;i++) { - setup_serve(&(g_array_index(servers, SERVER, i))); + want_modern |= setup_serve(&(g_array_index(servers, SERVER, i))); + } + if(want_modern) { + open_modern(); } children=g_hash_table_new_full(g_int_hash, g_int_equal, NULL, destroy_pid_t); + + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if(sigaction(SIGCHLD, &sa, NULL) == -1) + err("sigaction: %m"); + sa.sa_handler = sigterm_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if(sigaction(SIGTERM, &sa, NULL) == -1) + err("sigaction: %m"); } /**