/* * Copyright (c) 2010-2014 University of Utah and the Flux Group. * * {{{EMULAB-LICENSE * * This file is part of the Emulab network testbed software. * * This file is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * This file is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this file. If not, see . * * }}} */ /* * Frisbee master server. Listen for GET/PUT requests and fork off * handler processes. * * TODO: * - timeouts for child frisbee processes * - record the state of running frisbeeds in persistant store so that * they can be restarted if we die and restart * - related: make sure we don't leave orphans when we die! * - handle signals: INT/TERM should kill all frisbeeds and remove our pid */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_IGMP #include #endif #include "decls.h" #include "utils.h" #include "configdefs.h" #define FRISBEE_SERVER "/usr/testbed/sbin/frisbeed" #define FRISBEE_CLIENT "/usr/testbed/sbin/frisbee" #define FRISBEE_UPLOAD "/usr/testbed/sbin/frisuploadd" #define FRISBEE_RETRIES 5 static void get_options(int argc, char **argv); static int makesocket(int portnum, struct in_addr *ifip, int *tcpsockp); static void handle_request(int sock); static int reapchildren(int apid, int *status); #ifdef WITH_IGMP static void handle_igmp(void); #endif static char * gidstr(int ngids, gid_t gids[]); static int daemonize = 1; int debug = 0; static int dumpconfig = 0; static int onlymethods = (MS_METHOD_UNICAST|MS_METHOD_MULTICAST); static int parentmethods = (MS_METHOD_UNICAST|MS_METHOD_MULTICAST); static int myuid = NOUID; static int mygid = NOUID; /* * For recursively GETing images: * parentip/parentport address of a parent master server (-S,-P) * fetchfromabove true if a parent has been specified * canredirect if true, redirect our clients to our parent (-R) * usechildauth if true, pass client's authinfo to our parent (-A) */ static struct in_addr parentip; static int parentport = MS_PORTNUM; static int fetchfromabove = 0; static int canredirect = 0; static int usechildauth = 0; static int mirrormode = 0; static char *configstyle = "null"; static char *configopts = ""; static struct in_addr ifaceip; static int igmpqueryinterval = 0; /* XXX the following just keep network.c happy */ int portnum = MS_PORTNUM; int sockbufsize = SOCKBUFSIZE; struct in_addr mcastaddr; struct in_addr mcastif; int main(int argc, char **argv) { int tcpsock; FILE *fp; char buf[BUFSIZ]; char *pidfile = (char *) NULL; struct timeval tv; fd_set ready; #ifdef USE_LOCALHOST_PROXY int localsock = -1; #endif get_options(argc, argv); myuid = geteuid(); mygid = getegid(); if (daemonize && debug) { int odebug = debug; debug = 0; MasterServerLogInit(); debug = odebug; } else MasterServerLogInit(); FrisLog("mfrisbeed daemon starting as %d/%d, methods=%s (debug level %d)", myuid, mygid, GetMSMethods(onlymethods), debug); if (fetchfromabove) FrisLog(" using parent %s:%d%s, methods=%s", inet_ntoa(parentip), parentport, mirrormode ? " in mirror mode" : "", GetMSMethods(parentmethods)); #ifdef WITH_IGMP if (igmpqueryinterval) FrisLog(" acting as IGMP querier on %s with %d second interval", inet_ntoa(ifaceip), igmpqueryinterval); #endif config_init(configstyle, 1, configopts); /* Just dump the config to stdout in human readable form and exit. */ if (dumpconfig) { config_dump(stdout); exit(0); } /* * Create TCP server. */ if (makesocket(portnum, &ifaceip, &tcpsock) < 0) { FrisError("Could not make primary tcp socket!"); exit(1); } #ifdef USE_LOCALHOST_PROXY /* * Listen on localhost too for proxy requests. */ if (ifaceip.s_addr != htonl(INADDR_ANY) && ifaceip.s_addr != htonl(INADDR_LOOPBACK)) { struct in_addr localip; localip.s_addr = htonl(INADDR_LOOPBACK); if (makesocket(portnum, &localip, &localsock) < 0) { FrisError("Could not create localhost tcp socket!"); exit(1); } } #endif #ifdef WITH_IGMP if (igmpqueryinterval) IGMPInit(&ifaceip, NULL); #endif /* Now become a daemon */ if (daemonize) daemon(0, 0); /* * Stash the pid away. */ if (!geteuid()) { if (!pidfile) { sprintf(buf, "%s/mfrisbeed.pid", _PATH_VARRUN); pidfile = buf; } fp = fopen(pidfile, "w"); if (fp != NULL) { fprintf(fp, "%d\n", getpid()); (void) fclose(fp); } } /* * Handle connections */ FD_ZERO(&ready); while (1) { struct sockaddr_in client; socklen_t length; int newsock, rv, maxsock = 0; #ifdef WITH_IGMP /* Do the querier thing */ if (igmpqueryinterval) handle_igmp(); #endif FD_SET(tcpsock, &ready); maxsock = tcpsock + 1; #ifdef USE_LOCALHOST_PROXY if (localsock >= 0) { FD_SET(localsock, &ready); if (localsock > tcpsock) maxsock = localsock + 1; } #endif tv.tv_sec = 0; tv.tv_usec = 100000; rv = select(maxsock, &ready, NULL, NULL, &tv); if (rv < 0) { if (errno == EINTR) continue; FrisPfatal("select failed"); } if (rv) { int sock = tcpsock; #ifdef USE_LOCALHOST_PROXY if (localsock >= 0 && FD_ISSET(localsock, &ready)) sock = localsock; #endif length = sizeof(client); newsock = accept(sock, (struct sockaddr *)&client, &length); if (newsock < 0) FrisPwarning("accepting TCP connection"); else { fcntl(newsock, F_SETFD, FD_CLOEXEC); handle_request(newsock); close(newsock); } } (void) reapchildren(0, NULL); } close(tcpsock); #ifdef USE_LOCALHOST_PROXY if (localsock >= 0) close(localsock); #endif FrisLog("daemon terminating"); exit(0); } struct childinfo { struct childinfo *next; struct config_imageinfo *imageinfo; int ptype; int method; int pid; char *pidfile; int uid; /* UID to run child as */ gid_t gids[MAXGIDS]; /* GID to run child as */ int ngids; /* number of GIDs */ int retries; /* # times to try starting up child */ int timeout; /* max runtime (sec) for child */ in_addr_t servaddr; /* -S arg */ in_addr_t ifaceaddr; /* -i arg */ in_addr_t addr; /* -m arg */ in_port_t port; /* -p arg */ void (*done)(struct childinfo *, int); void *extra; }; #define PTYPE_CLIENT 1 #define PTYPE_SERVER 2 #define PTYPE_UPLOADER 4 struct clientextra { char *realname; char *resolvedname; /* info from our parent */ uint16_t sigtype; uint8_t signature[MS_MAXSIGLEN]; uint32_t hisize; uint32_t losize; }; struct uploadextra { char *realname; uint64_t isize; uint32_t mtime; int itimeout; }; static struct childinfo *findchild(char *, int, int); static int startchild(struct childinfo *); static struct childinfo *startserver(struct config_imageinfo *, in_addr_t, in_addr_t, int, int *); static struct childinfo *startclient(struct config_imageinfo *, in_addr_t, in_addr_t, in_addr_t, in_port_t, int, int *); static struct childinfo *startuploader(struct config_imageinfo *, in_addr_t, in_addr_t, uint64_t, uint32_t, int, int *); static void free_imageinfo(struct config_imageinfo *ii) { if (ii) { if (ii->imageid) free(ii->imageid); if (ii->dir) free(ii->dir); if (ii->path) free(ii->path); if (ii->sig) free(ii->sig); if (ii->get_options) free(ii->get_options); if (ii->put_oldversion) free(ii->put_oldversion); if (ii->put_options) free(ii->put_options); free(ii); } } /* * (Deep) copy an imageinfo structure. * Returns pointer or null on error. */ static struct config_imageinfo * copy_imageinfo(struct config_imageinfo *ii) { struct config_imageinfo *nii; int i; if ((nii = calloc(1, sizeof *nii)) == NULL) goto fail; if (ii->imageid && (nii->imageid = strdup(ii->imageid)) == NULL) goto fail; if (ii->dir && (nii->dir = strdup(ii->dir)) == NULL) goto fail; if (ii->path && (nii->path = strdup(ii->path)) == NULL) goto fail; if (ii->sig) { int sz = 0; if (ii->flags & CONFIG_SIG_ISMTIME) sz = sizeof(time_t); if (sz) { if ((nii->sig = malloc(sz)) == NULL) goto fail; } if (nii->sig) memcpy(nii->sig, ii->sig, sz); } nii->flags = ii->flags; nii->uid = ii->uid; for (i = 0; i < ii->ngids; i++) nii->gids[i] = ii->gids[i]; nii->ngids = ii->ngids; if (ii->get_options && (nii->get_options = strdup(ii->get_options)) == NULL) goto fail; nii->get_methods = ii->get_methods; nii->get_timeout = ii->get_timeout; if (ii->put_oldversion && (nii->put_oldversion = strdup(ii->put_oldversion)) == NULL) goto fail; if (ii->put_options && (nii->put_options = strdup(ii->put_options)) == NULL) goto fail; nii->put_maxsize = ii->put_maxsize; nii->put_timeout = ii->put_timeout; nii->put_itimeout = ii->put_itimeout; /* XXX don't care about extra right now */ return nii; fail: free_imageinfo(nii); return NULL; } /* * Fetch an image from our parent by getting image info from it and firing * off a frisbee. If canredirect is non-zero, we also hook our child up * directly with our parent so that it can fetch in parallel. If statusonly * is non-zero, we just request info from our parent and return that. * Returns zero if Reply struct contains the desired info, TRYAGAIN if we * have started up a frisbee to fetch from our parent, or an error otherwise. * Status is returned in host order. */ int fetch_parent(struct in_addr *myip, struct in_addr *hostip, struct in_addr *pip, in_port_t pport, struct config_imageinfo *ii, int statusonly, GetReply *replyp) { struct childinfo *ci; struct clientextra *ce; struct in_addr pif; in_addr_t authip; GetReply reply; int rv, methods; /* * If usechildauth is set, we pass the child host IP to our parent * for authentication, otherwise we use our own. */ authip = usechildauth ? ntohl(hostip->s_addr) : 0; /* * Allowed image methods are constrained by any parent methods */ methods = ii->get_methods & parentmethods; if (methods == 0) return MS_ERROR_NOMETHOD; /* * See if a fetch is already in progress. * If so we will either return "try again later" or point them to * our parent. */ ci = findchild(ii->imageid, PTYPE_CLIENT, methods); if (ci != NULL) { if (debug) FrisInfo("%s: fetch from %s in progress", ii->imageid, inet_ntoa(*pip)); /* * Since a download is in progress we don't normally need * to revalidate since we wouldn't be downloading the image * if we didn't have access. * * However, when acting for a child, we do need to validate * every time since the child making the current request * might not have the same access as the child we are * currently downloading for. */ if (authip) { if (!ClientNetFindServer(ntohl(pip->s_addr), pport, authip, ii->imageid, methods, 1, 5, &reply, &pif)) return MS_ERROR_NOIMAGE; if (reply.error) return reply.error; } /* * We return the info we got from our parent. */ assert(ci->extra != NULL); ce = ci->extra; memset(replyp, 0, sizeof *replyp); replyp->sigtype = ce->sigtype; memcpy(replyp->signature, ce->signature, MS_MAXSIGLEN); replyp->hisize = ce->hisize; replyp->losize = ce->losize; if (statusonly) { if (debug) FrisInfo("%s: parent status: " "sigtype=%d, sig=0x%08x..., size=%x/%x", ii->imageid, ce->sigtype, *(uint32_t *)ce->signature, ce->hisize, ce->losize); return 0; } /* * A real get request. * We either tell them to try again later or redirect them * to our parent. */ goto done; } if (debug) FrisInfo("%s: requesting %simage from %s:%d", ii->imageid, (statusonly ? "status of ": ""), inet_ntoa(*pip), pport); /* * Image fetch is not in progress. * Send our parent a GET request and see what it says. */ if (!ClientNetFindServer(ntohl(pip->s_addr), pport, authip, ii->imageid, methods, statusonly, 5, &reply, &pif)) return MS_ERROR_NOIMAGE; if (reply.error) return reply.error; /* * Return the image size and signature from our parent */ memset(replyp, 0, sizeof *replyp); replyp->sigtype = reply.sigtype; memcpy(replyp->signature, reply.signature, MS_MAXSIGLEN); replyp->hisize = reply.hisize; replyp->losize = reply.losize; if (statusonly) { if (debug) FrisInfo("%s: parent status: " "sigtype=%d, sig=0x%08x..., size=%x/%x", ii->imageid, reply.sigtype, *(uint32_t *)reply.signature, reply.hisize, reply.losize); return 0; } /* * Parent has started up a frisbeed, spawn a frisbee to capture it. * * Note that servaddr from the reply might not be our parent (pip), * if our parent in turn had to ask its parent and redirect was in * effect. Unfortunately, in this case, 'pif' might not be correct * since it is always the interface from which our parent responded. * It will work as long as our parent's ancestor(s) reach us via * the same interface. */ ci = startclient(ii, ntohl(pif.s_addr), reply.servaddr, reply.addr, reply.port, reply.method, &rv); if (ci == NULL) return rv; /* * Cache the size/signature info for use in future calls when * we are busy (the case above). */ assert(ci->extra != NULL); ce = ci->extra; ce->sigtype = replyp->sigtype; memcpy(ce->signature, replyp->signature, MS_MAXSIGLEN); ce->hisize = replyp->hisize; ce->losize = replyp->losize; if (debug) FrisInfo("%s cache status: " "sigtype=%d, sig=0x%08x..., size=%x/%x", ii->imageid, ce->sigtype, *(uint32_t *)ce->signature, ce->hisize, ce->losize); done: if (!canredirect) return MS_ERROR_TRYAGAIN; /* * XXX more unicast "fer now" hackary...if we are talking unicast with * our parent, we cannot redirect the child there as well. */ if (ci->method == MS_METHOD_UNICAST) return MS_ERROR_TRYAGAIN; memset(replyp, 0, sizeof *replyp); replyp->method = ci->method; replyp->servaddr = ci->servaddr; replyp->addr = ci->addr; replyp->port = ci->port; return 0; } /* * Handle a GET request. * * This can be either a request to get the actual image (status==0) * or a request to get just the status of the image (status==1). * * A request for the actual image causes us to start up a frisbee server * for the image if one is not already running. If we are configured with * a parent, we may need to first fetch the image from our parent with a * GET request. In this case, we may return a TRYAGAIN status while the * image transfer is in progress. Alternatively, if we are configured to * allow redirection, we could just point our client to our parent and allow * them to download the image along side us. * * A request for status will not fire up a server, but may require a status * call on our parent. A status call will never return TRYAGAIN, it will * always return the most recent status for an image or some other error. * If we have a parent, we will always get its status for the image and * return that if "newer" than ours. Thus status is a synchronous call that * will return the attributes of the image that will ultimately be transferred * to the client upon a GET request. */ void handle_get(int sock, struct sockaddr_in *sip, struct sockaddr_in *cip, MasterMsg_t *msg) { struct in_addr host; char imageid[MS_MAXIDLEN+1], *cimageid; char clientip[sizeof("XXX.XXX.XXX.XXX")+1]; int len; struct config_host_authinfo *ai; struct config_imageinfo *ii = NULL; struct childinfo *ci; struct stat sb; uint64_t isize; int rv, methods, wantstatus; int getfromparent; GetReply reply; char *op; /* * If an explicit host was listed, use that as the host we are * authenticating, otherwise use the caller's IP. config_auth_by_IP * will reject the former if the caller is not allowed to proxy for * the node in question. */ if (msg->body.getrequest.hostip) host.s_addr = msg->body.getrequest.hostip; else host.s_addr = cip->sin_addr.s_addr; len = ntohs(msg->body.getrequest.idlen); memcpy(imageid, msg->body.getrequest.imageid, len); imageid[len] = '\0'; cimageid = config_canonicalize_imageid(imageid); if (cimageid != NULL && strcmp(cimageid, imageid) == 0) { free(cimageid); cimageid = NULL; } methods = msg->body.getrequest.methods; wantstatus = msg->body.getrequest.status; op = wantstatus ? "GETSTATUS" : "GET"; strncpy(clientip, inet_ntoa(cip->sin_addr), sizeof clientip); if (cimageid == NULL) { if (host.s_addr != cip->sin_addr.s_addr) FrisLog("%s: %s from %s (for %s), methods: 0x%x", imageid, op, clientip, inet_ntoa(host), methods); else FrisLog("%s: %s from %s, (methods: 0x%x)", imageid, op, clientip, methods); } else { if (host.s_addr != cip->sin_addr.s_addr) FrisLog("%s (%s): %s from %s (for %s), methods: 0x%x", imageid, cimageid, op, clientip, inet_ntoa(host), methods); else FrisLog("%s (%s): %s from %s, (methods: 0x%x)", imageid, cimageid, op, clientip, methods); } memset(msg, 0, sizeof *msg); msg->hdr.type = htonl(MS_MSGTYPE_GETREPLY); strncpy((char *)msg->hdr.version, MS_MSGVERS_1, sizeof(msg->hdr.version)); /* * If they request a method we don't support, reject them before * we do any other work. XXX maybe the method should not matter * for a status-only call? */ methods &= onlymethods; if (methods == 0) goto badmethod; /* * Use the canonical name from here on out. */ if (cimageid != NULL) { strcpy(imageid, cimageid); free(cimageid); cimageid = NULL; } /* * In mirrormode, we first validate access with our parent * before doing anything locally. */ if (mirrormode) { struct in_addr pif; in_addr_t authip; authip = usechildauth ? ntohl(host.s_addr) : 0; if (!ClientNetFindServer(ntohl(parentip.s_addr), parentport, authip, imageid, methods, 1, 5, &reply, &pif)) reply.error = MS_ERROR_NOIMAGE; if (reply.error) { msg->body.getreply.error = reply.error; FrisLog("%s: client %s authentication with parent failed: %s", imageid, clientip, GetMSError(reply.error)); goto reply; } } #ifdef USE_LOCALHOST_PROXY /* * XXX this is really an Emulab special case for a boss node. * * We allow localhost access to any image that has a daemon * currently running. This is a hack to allow localhost to get * info for a running daemon so it can kill it. * * Perhaps localhost should be able to access ANY valid image * or image directory (running or not), but I don't want to go * there right now... */ if (wantstatus && cip->sin_addr.s_addr == htonl(INADDR_LOOPBACK) && host.s_addr == htonl(INADDR_LOOPBACK) && (ci = findchild(imageid, PTYPE_SERVER, methods)) != NULL) { /* XXX only fill in the info that the caller cares about */ msg->body.getreply.method = ci->method; msg->body.getreply.isrunning = 1; msg->body.getreply.addr = htonl(ci->addr); msg->body.getreply.port = htons(ci->port); goto reply; } #endif /* * See if node has access to the image. * If not, return an error code immediately. */ rv = config_auth_by_IP(1, &cip->sin_addr, &host, imageid, &ai); if (rv) { FrisWarning("%s: client %s %s failed: %s", imageid, clientip, op, GetMSError(rv)); msg->body.getreply.error = rv; goto reply; } if (debug > 1) config_dump_host_authinfo(ai); if (ai->numimages > 1) { rv = MS_ERROR_INVALID; FrisWarning("%s: client %s %s failed: " "lookup returned multiple (%d) images", imageid, clientip, op, ai->numimages); msg->body.getreply.error = rv; goto reply; } ii = copy_imageinfo(&ai->imageinfo[0]); config_free_host_authinfo(ai); assert((ii->flags & CONFIG_PATH_ISFILE) != 0); /* * If the image is currently being uploaded, return TRYAGAIN. */ if (findchild(ii->imageid, PTYPE_UPLOADER, MS_METHOD_UNICAST)) { rv = MS_ERROR_TRYAGAIN; FrisLog("%s: %s currently being uploaded", imageid, op); msg->body.getreply.error = rv; goto reply; } /* * See if image actually exists. * * If the file exists but is not a regular file, we return an error. * * If the file does not exist (it is possible to request an image * that doesn't exist if the authentication check allows access to * the containing directory) and we have a parent, we request it * from the parent. */ isize = 0; getfromparent = 0; if ((ii->flags & CONFIG_PATH_EXISTS) != 0 && stat(ii->path, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { rv = MS_ERROR_INVALID; FrisWarning("%s: client %s %s failed: " "not a regular file", imageid, clientip, op); msg->body.getreply.error = rv; goto reply; } isize = sb.st_size; /* * If the file exists and we have a parent, get the signature * from the parent and see if we need to update our copy. * We only do this for mirror mode. */ if (mirrormode) { FrisLog("%s: have local copy", imageid); /* * See if the signature is out of date. * Since this is mirror mode, we can use the status * info we got from the earlier call. * * Note that we actually check for a different date, * not just a newer date. People often roll back * faulty images to previous versions. * * XXX need checks for other signature types. */ if ((reply.sigtype == MS_SIGTYPE_MTIME && *(time_t *)reply.signature != sb.st_mtime)) { uint32_t mt = *(uint32_t *)reply.signature; msg->body.getreply.sigtype = htons(reply.sigtype); if (reply.sigtype == MS_SIGTYPE_MTIME) *(uint32_t *)reply.signature = htonl(mt); memcpy(msg->body.getreply.signature, reply.signature, MS_MAXSIGLEN); msg->body.getreply.hisize = htonl(reply.hisize); msg->body.getreply.losize = htonl(reply.losize); if (wantstatus) goto reply; FrisLog("%s: local copy (sig=%x) " "is out of date (sig=%x), GET from parent", imageid, sb.st_mtime, mt); getfromparent = 1; } } } else if (fetchfromabove) { FrisLog("%s: no local copy, %s from parent", imageid, op); /* * We don't have the image, but we have a parent, * request the status as we did above. Again, in mirrormode, * we have already fetched the status so no additional call * is needed. * * Any error is reflected to our caller. */ if (!mirrormode) { rv = fetch_parent(&sip->sin_addr, &host, &parentip, parentport, ii, 1, &reply); if (rv) { FrisLog("%s: failed getting parent status: %s, " "failing", imageid, GetMSError(rv)); msg->body.getreply.error = rv; goto reply; } } /* * And we must always fetch from the parent. * Return the attributes we got via the check above. */ msg->body.getreply.sigtype = htons(reply.sigtype); if (reply.sigtype == MS_SIGTYPE_MTIME) { uint32_t mt; mt = *(uint32_t *)reply.signature; *(uint32_t *)reply.signature = htonl(mt); } memcpy(msg->body.getreply.signature, reply.signature, MS_MAXSIGLEN); msg->body.getreply.hisize = htonl(reply.hisize); msg->body.getreply.losize = htonl(reply.losize); if (wantstatus) goto reply; getfromparent = 1; } else { /* * No image and no parent, just flat fail. */ rv = (errno == ENOENT) ? MS_ERROR_NOIMAGE : MS_ERROR_INVALID; FrisWarning("%s: client %s %s failed: %s", imageid, clientip, op, GetMSError(rv)); msg->body.getreply.error = rv; goto reply; } /* * Either we did not have the image or our copy is out of date, * attempt to fetch from our parent. */ if (getfromparent) { rv = fetch_parent(&sip->sin_addr, &host, &parentip, parentport, ii, 0, &reply); /* * Redirecting to parent. * Can only do this if our parents method is compatible * with the client's request. */ if (rv == 0) { if ((reply.method & methods) != 0) { msg->body.getreply.method = reply.method; msg->body.getreply.isrunning = 1; msg->body.getreply.servaddr = htonl(reply.servaddr); msg->body.getreply.addr = htonl(reply.addr); msg->body.getreply.port = htons(reply.port); FrisLog("%s: redirecting %s to our parent", imageid, clientip); goto reply; } FrisLog("%s: cannot redirect %s to parent; " "incompatible transfer methods", imageid, clientip); rv = MS_ERROR_TRYAGAIN; } /* * If parent callout failed, but we have a copy * use our stale version. */ if (rv != MS_ERROR_TRYAGAIN && isize > 0) { FrisWarning("%s: client %s %s from parent failed: %s, " "using our stale copy", imageid, clientip, op, GetMSError(rv)); } /* * Otherwise, we are busy fetching the new copy (TRYAGAIN), * or we had a real failure and we don't have a copy. */ else { FrisWarning("%s: client %s %s from parent failed: %s", imageid, clientip, op, GetMSError(rv)); msg->body.getreply.error = rv; goto reply; } } /* * Figure out what transfer method to use. */ if (debug) FrisInfo(" request methods: 0x%x, image methods: 0x%x", methods, ii->get_methods); methods &= ii->get_methods; if (methods == 0) { badmethod: rv = MS_ERROR_NOMETHOD; FrisWarning("%s: client %s %s failed: %s", imageid, clientip, op, GetMSError(rv)); msg->body.getreply.error = rv; goto reply; } /* * Otherwise see if there is a frisbeed already running, starting * one if not. Then construct a reply with the available info. */ ci = findchild(ii->imageid, PTYPE_SERVER, methods); /* * XXX right now frisbeed doesn't support mutiple clients * on the same unicast address. We could do multiple servers * for the same image, but we don't. */ if (!wantstatus && ci != NULL && ci->method == MS_METHOD_UNICAST) { FrisWarning("%s: client %s %s failed: " "unicast server already running", imageid, clientip, op); msg->body.getreply.error = MS_ERROR_TRYAGAIN; goto reply; } if (ci == NULL) { struct in_addr in; in_addr_t myaddr; int stat; if (ii->sig != NULL) { int32_t mt = *(int32_t *)ii->sig; assert((ii->flags & CONFIG_SIG_ISMTIME) != 0); msg->body.getreply.sigtype = htons(MS_SIGTYPE_MTIME); *(int32_t *)msg->body.getreply.signature = htonl(mt); } if (wantstatus) { msg->body.getreply.method = methods; msg->body.getreply.isrunning = 0; msg->body.getreply.hisize = htonl(isize >> 32); msg->body.getreply.losize = htonl(isize); FrisLog("%s: STATUS is not running", imageid); goto reply; } myaddr = ntohl(sip->sin_addr.s_addr); #ifdef USE_LOCALHOST_PROXY /* * If this was a proxy request from localhost, * we need to start the server on the real interface * instead. If none was specified, we don't know what * interface to use, so just fail. */ if (myaddr == INADDR_LOOPBACK) { myaddr = ntohl(ifaceip.s_addr); if (myaddr == INADDR_ANY) { FrisWarning("%s: cannot start server on behalf " "of %s", imageid, clientip); msg->body.getreply.error = MS_ERROR_FAILED; goto reply; } } #endif ci = startserver(ii, myaddr, ntohl(cip->sin_addr.s_addr), methods, &rv); if (ci == NULL) { msg->body.getreply.error = rv; goto reply; } in.s_addr = htonl(ci->addr); FrisLog("%s: started %s server on %s:%d (pid %d, timo %ds)", imageid, GetMSMethods(ci->method), inet_ntoa(in), ci->port, ci->pid, ci->timeout); if (debug) { FrisLog(" uid: %d, gids: %s", ci->uid, gidstr(ci->ngids, ci->gids)); } /* * Watch for an immediate death so we don't tell our client * a server is running when it really isn't. * * XXX what is the right response? Right now we just tell * them to try again and hope the problem is transient. */ sleep(2); if (reapchildren(ci->pid, &stat)) { msg->body.getreply.error = MS_ERROR_TRYAGAIN; FrisLog("%s: server immediately exited (stat=0x%x), " "telling client to try again!", imageid, stat); goto reply; } } else { struct in_addr in; if (ii->sig != NULL) { int32_t mt = *(int32_t *)ii->sig; assert((ii->flags & CONFIG_SIG_ISMTIME) != 0); msg->body.getreply.sigtype = htons(MS_SIGTYPE_MTIME); *(int32_t *)msg->body.getreply.signature = htonl(mt); } in.s_addr = htonl(ci->addr); if (wantstatus) FrisLog("%s: STATUS is running %s on %s:%d (pid %d)", imageid, GetMSMethods(ci->method), inet_ntoa(in), ci->port, ci->pid); else FrisLog("%s: %s server already running on %s:%d (pid %d)", imageid, GetMSMethods(ci->method), inet_ntoa(in), ci->port, ci->pid); if (debug) FrisLog(" uid: %d, gids: %s", ci->uid, gidstr(ci->ngids, ci->gids)); } msg->body.getreply.hisize = htonl(isize >> 32); msg->body.getreply.losize = htonl(isize); msg->body.getreply.method = ci->method; msg->body.getreply.isrunning = 1; msg->body.getreply.servaddr = htonl(ci->servaddr); /* * XXX tmp hack. Currently, if we are unicasting, startserver * returns the addr field set to the address of the client * (see the XXX there for details). However, we need to return our * address to the client. * * When frisbeed is changed to support more than one unicast client, * this will change. */ if (ci->method == MS_METHOD_UNICAST) msg->body.getreply.addr = msg->body.getreply.servaddr; else msg->body.getreply.addr = htonl(ci->addr); msg->body.getreply.port = htons(ci->port); reply: msg->body.getreply.error = htons(msg->body.getreply.error); if (debug) { FrisInfo("%s reply: sigtype=%d, sig=0x%08x..., size=%x/%x", op, ntohs(msg->body.getreply.sigtype), ntohl(*(uint32_t *)msg->body.getreply.signature), ntohl(msg->body.getreply.hisize), ntohl(msg->body.getreply.losize)); } len = sizeof msg->hdr + sizeof msg->body.getreply; if (!MsgSend(sock, msg, len, 10)) FrisError("%s: could not send reply", inet_ntoa(cip->sin_addr)); if (ii) free_imageinfo(ii); } void handle_put(int sock, struct sockaddr_in *sip, struct sockaddr_in *cip, MasterMsg_t *msg) { struct in_addr host, in; in_addr_t myaddr; char imageid[MS_MAXIDLEN+1]; char clientip[sizeof("XXX.XXX.XXX.XXX")+1]; int len; struct config_host_authinfo *ai; struct config_imageinfo *ii = NULL; struct childinfo *ci; struct stat sb; uint64_t isize; uint32_t mtime, timo; int rv, wantstatus; char *op; /* * If an explicit host was listed, use that as the host we are * authenticating, otherwise use the caller's IP. config_auth_by_IP * will reject the former if the caller is not allowed to proxy for * the node in question. */ if (msg->body.putrequest.hostip) host.s_addr = msg->body.putrequest.hostip; else host.s_addr = cip->sin_addr.s_addr; len = ntohs(msg->body.putrequest.idlen); memcpy(imageid, msg->body.putrequest.imageid, len); imageid[len] = '\0'; wantstatus = msg->body.putrequest.status; op = wantstatus ? "PUTSTATUS" : "PUT"; isize = ((uint64_t)ntohl(msg->body.putrequest.hisize) << 32) | ntohl(msg->body.putrequest.losize); mtime = ntohl(msg->body.putrequest.mtime); timo = ntohl(msg->body.putrequest.timeout); strncpy(clientip, inet_ntoa(cip->sin_addr), sizeof clientip); if (host.s_addr != cip->sin_addr.s_addr) FrisLog("%s: %s from %s (for %s), size=%llu", imageid, op, clientip, inet_ntoa(host), isize); else FrisLog("%s: %s from %s, size=%llu", imageid, op, clientip, isize); memset(msg, 0, sizeof *msg); msg->hdr.type = htonl(MS_MSGTYPE_PUTREPLY); strncpy((char *)msg->hdr.version, MS_MSGVERS_1, sizeof(msg->hdr.version)); /* * XXX we don't handle mirror mode right now. */ if (mirrormode) { rv = MS_ERROR_NOTIMPL; FrisWarning("%s: client %s %s failed: " "upload not supported in mirror mode", imageid, clientip, op); msg->body.putreply.error = rv; goto reply; } /* * See if node has access to the image. * If not, return an error code immediately. */ rv = config_auth_by_IP(0, &cip->sin_addr, &host, imageid, &ai); if (rv) { FrisWarning("%s: client %s %s failed: %s", imageid, clientip, op, GetMSError(rv)); msg->body.putreply.error = rv; goto reply; } if (debug > 1) config_dump_host_authinfo(ai); if (ai->numimages > 1) { rv = MS_ERROR_INVALID; FrisWarning("%s: client %s %s failed: " "lookup returned multiple (%d) images", imageid, clientip, op, ai->numimages); msg->body.putreply.error = rv; goto reply; } ii = copy_imageinfo(&ai->imageinfo[0]); config_free_host_authinfo(ai); assert((ii->flags & CONFIG_PATH_ISFILE) != 0); /* * If they gave us a size and it exceeds the maxsize, return an error. * We do this even for a status-only request; they can specify a size * of zero if they want to get all the image attributes. */ if (isize > ii->put_maxsize) { rv = MS_ERROR_TOOBIG; FrisWarning("%s: client %s %s failed: " "upload size (%llu) exceeds maximum for image (%llu)", imageid, clientip, op, isize, ii->put_maxsize); /* * We return the max size here along with the error so that * the client doesn't have to make a second, wantstatus call. */ msg->body.putreply.himaxsize = htonl(ii->put_maxsize >> 32); msg->body.putreply.lomaxsize = htonl(ii->put_maxsize); msg->body.putreply.error = rv; goto reply; } /* * They gave us an mtime and it is "bad", return an error. * XXX somewhat arbitrary: cannot set a time in the future. * XXX cut them some slack on the future time thing, up to 10s okay. */ if (mtime) { struct timeval now; gettimeofday(&now, NULL); if (mtime > (now.tv_sec + 10)) { rv = MS_ERROR_BADMTIME; FrisWarning("%s: client %s %s failed: " "attempt to set mtime in the future " "(%u > %u)\n", imageid, clientip, op, mtime, now.tv_sec); msg->body.putreply.error = rv; goto reply; } } /* * If the image is being served, fetched from our parent, * or uploaded--return TRYAGAIN. */ if ((ci = findchild(ii->imageid, PTYPE_SERVER, onlymethods)) || (ci = findchild(ii->imageid, PTYPE_CLIENT, onlymethods)) || (ci = findchild(ii->imageid, PTYPE_UPLOADER, MS_METHOD_UNICAST))) { msg->body.putreply.error = MS_ERROR_TRYAGAIN; FrisLog("%s: %s currently being %s", imageid, op, (ci->ptype == PTYPE_SERVER) ? "served" : (ci->ptype == PTYPE_CLIENT) ? "downloaded from parent" : "uploaded"); goto reply; } /* * See if image actually exists. * * If the file exists but is not a regular file, we return an error. * (maybe we should just remove it?) * * If it is a regular file, we return its current signature. * * If the file does not exist, that is okay (the authentication check * has verified that the node can create the image). */ if ((ii->flags & CONFIG_PATH_EXISTS) != 0 && stat(ii->path, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { rv = MS_ERROR_INVALID; FrisWarning("%s: client %s %s failed: " "existing target is not a regular file", imageid, clientip, op); msg->body.putreply.error = rv; goto reply; } msg->body.putreply.exists = 1; /* * Return the current size and signature info */ msg->body.putreply.hisize = htonl((uint64_t)sb.st_size >> 32); msg->body.putreply.losize = htonl(sb.st_size); if (ii->sig != NULL) { int32_t mt = *(int32_t *)ii->sig; assert((ii->flags & CONFIG_SIG_ISMTIME) != 0); msg->body.putreply.sigtype = htons(MS_SIGTYPE_MTIME); *(int32_t *)msg->body.putreply.signature = htonl(mt); } } else { msg->body.putreply.exists = 0; } /* * At this point we know that there is no conflicting use of the * image in progress, so either return status or fire off an uploader * and return the contact info to the client. */ msg->body.putreply.himaxsize = htonl(ii->put_maxsize >> 32); msg->body.putreply.lomaxsize = htonl(ii->put_maxsize); if (wantstatus) { FrisLog("%s: PUTSTATUS upload allowed", imageid); goto reply; } myaddr = ntohl(sip->sin_addr.s_addr); ci = startuploader(ii, myaddr, ntohl(cip->sin_addr.s_addr), isize, mtime, (int)timo, &rv); if (ci == NULL) { msg->body.putreply.error = rv; goto reply; } in.s_addr = htonl(ci->addr); FrisLog("%s: started uploader on %s:%d (pid %d, timo %ds)", imageid, inet_ntoa(in), ci->port, ci->pid, ci->timeout); if (debug) FrisLog(" uid: %d, gids: %s", ci->uid, gidstr(ci->ngids, ci->gids)); /* * Watch for an immediate death so we don't tell our client * an uploader is running when it really isn't. * * XXX what is the right response? Right now we just tell * them to try again and hope the problem is transient. */ sleep(2); if (reapchildren(ci->pid, &rv)) { msg->body.putreply.error = MS_ERROR_TRYAGAIN; FrisLog("%s: uploader immediately exited (stat=0x%x), " "telling client to try again!", imageid, rv); goto reply; } msg->body.putreply.addr = htonl(ci->servaddr); msg->body.putreply.port = htons(ci->port); reply: msg->body.putreply.error = htons(msg->body.putreply.error); if (debug) { FrisInfo("%s reply: sigtype=%d, sig=0x%08x..., " "size=%x/%x, maxsize=%x/%x", op, ntohs(msg->body.putreply.sigtype), ntohl(*(uint32_t *)msg->body.putreply.signature), ntohl(msg->body.putreply.hisize), ntohl(msg->body.putreply.losize), ntohl(msg->body.putreply.himaxsize), ntohl(msg->body.putreply.lomaxsize)); } len = sizeof msg->hdr + sizeof msg->body.putreply; if (!MsgSend(sock, msg, len, 10)) FrisError("%s: could not send reply", inet_ntoa(cip->sin_addr)); if (ii) free_imageinfo(ii); } static void handle_request(int sock) { MasterMsg_t msg; int cc; struct sockaddr_in me, you; socklen_t len; len = sizeof me; if (getsockname(sock, (struct sockaddr *)&me, &len) < 0) { perror("getsockname"); return; } len = sizeof you; if (getpeername(sock, (struct sockaddr *)&you, &len) < 0) { perror("getpeername"); return; } cc = read(sock, &msg, sizeof msg); if (cc < sizeof msg.hdr) { if (cc < 0) perror("request message failed"); else FrisError("request message too small"); return; } switch (ntohl(msg.hdr.type)) { case MS_MSGTYPE_GETREQUEST: if (cc < sizeof msg.hdr + sizeof msg.body.getrequest) FrisError("GET request message too small"); else handle_get(sock, &me, &you, &msg); break; case MS_MSGTYPE_PUTREQUEST: if (cc < sizeof msg.hdr + sizeof msg.body.putrequest) FrisError("PUT request message too small"); else handle_put(sock, &me, &you, &msg); break; default: FrisError("unrecognized message type %d", ntohl(msg.hdr.type)); break; } } static void usage(void) { fprintf(stderr, "mfrisbeed [-ADRd] [-X method] [-I imagedir] [-S parentIP] [-P parentport] [-p port]\n"); fprintf(stderr, "Basic:\n"); fprintf(stderr, " -C