/* * Create a credential info object from a Munge context */ static munge_info_t * cred_info_create(munge_ctx_t ctx) { munge_err_t e; munge_info_t *mi = cred_info_alloc(); e = munge_ctx_get(ctx, MUNGE_OPT_ENCODE_TIME, &mi->encoded); if (e != EMUNGE_SUCCESS) error ("auth_munge: Unable to retrieve encode time: %s", munge_ctx_strerror(ctx)); e = munge_ctx_get(ctx, MUNGE_OPT_DECODE_TIME, &mi->decoded); if (e != EMUNGE_SUCCESS) error ("auth_munge: Unable to retrieve decode time: %s", munge_ctx_strerror(ctx)); e = munge_ctx_get(ctx, MUNGE_OPT_CIPHER_TYPE, &mi->cipher); if (e != EMUNGE_SUCCESS) error ("auth_munge: Unable to retrieve cipher type: %s", munge_ctx_strerror(ctx)); e = munge_ctx_get(ctx, MUNGE_OPT_MAC_TYPE, &mi->mac); if (e != EMUNGE_SUCCESS) error ("auth_munge: Unable to retrieve mac type: %s", munge_ctx_strerror(ctx)); e = munge_ctx_get(ctx, MUNGE_OPT_ZIP_TYPE, &mi->zip); if (e != EMUNGE_SUCCESS) error ("auth_munge: Unable to retrieve zip type: %s", munge_ctx_strerror(ctx)); return mi; }
/* NOTE: Caller must xfree the signature returned by sig_pp */ extern int crypto_sign(void * key, char *buffer, int buf_size, char **sig_pp, unsigned int *sig_size_p) { int retry = RETRY_COUNT, auth_ttl; char *cred; munge_err_t err; munge_ctx_t ctx = (munge_ctx_t) key; auth_ttl = slurm_get_auth_ttl(); if (auth_ttl) (void) munge_ctx_set(ctx, MUNGE_OPT_TTL, auth_ttl); again: err = munge_encode(&cred, ctx, buffer, buf_size); if (err != EMUNGE_SUCCESS) { if ((err == EMUNGE_SOCKET) && retry--) { debug("Munge encode failed: %s (retrying ...)", munge_ctx_strerror(ctx)); usleep(RETRY_USEC); /* Likely munged too busy */ goto again; } if (err == EMUNGE_SOCKET) /* Also see MUNGE_OPT_TTL above */ error("If munged is up, restart with --num-threads=10"); return err; } *sig_size_p = strlen(cred) + 1; *sig_pp = xstrdup(cred); free(cred); return 0; }
extern int crypto_verify_sign(void * key, char *buffer, unsigned int buf_size, char *signature, unsigned int sig_size) { int retry = RETRY_COUNT; uid_t uid; gid_t gid; void *buf_out = NULL; int buf_out_size; int rc = 0; munge_err_t err; munge_ctx_t ctx = (munge_ctx_t) key; again: err = munge_decode(signature, ctx, &buf_out, &buf_out_size, &uid, &gid); if (err != EMUNGE_SUCCESS) { if ((err == EMUNGE_SOCKET) && retry--) { debug("Munge decode failed: %s (retrying ...)", munge_ctx_strerror(ctx)); usleep(RETRY_USEC); /* Likely munged too busy */ goto again; } if (err == EMUNGE_SOCKET) error("If munged is up, restart with --num-threads=10"); #ifdef MULTIPLE_SLURMD if (err != EMUNGE_CRED_REPLAYED) { rc = err; goto end_it; } else { debug2("We had a replayed crypto, but this " "is expected in multiple slurmd mode."); } #else if (err == EMUNGE_CRED_REPLAYED) rc = ESIG_CRED_REPLAYED; else rc = err; goto end_it; #endif } if ((uid != slurm_user) && (uid != 0)) { error("crypto/munge: Unexpected uid (%d) != SLURM uid (%d)", (int) uid, (int) slurm_user); rc = ESIG_BAD_USERID; } else if (buf_size != buf_out_size) rc = ESIG_BUF_SIZE_MISMATCH; else if (memcmp(buffer, buf_out, buf_size)) rc = ESIG_BUF_DATA_MISMATCH; end_it: if (buf_out) free(buf_out); return rc; }
extern void * crypto_read_private_key(const char *path) { munge_ctx_t ctx; munge_err_t err; char *socket; int auth_ttl, rc; if ((ctx = munge_ctx_create()) == NULL) { error ("crypto_read_private_key: munge_ctx_create failed"); return (NULL); } socket = _auth_opts_to_socket(); if (socket) { rc = munge_ctx_set(ctx, MUNGE_OPT_SOCKET, socket); xfree(socket); if (rc != EMUNGE_SUCCESS) { error("munge_ctx_set failure"); munge_ctx_destroy(ctx); return NULL; } } auth_ttl = slurm_get_auth_ttl(); if (auth_ttl) (void) munge_ctx_set(ctx, MUNGE_OPT_TTL, auth_ttl); /* * Only allow slurmd_user (usually root) to decode job * credentials created by * slurmctld. This provides a slight layer of extra security, * as non-privileged users cannot get at the contents of job * credentials. */ err = munge_ctx_set(ctx, MUNGE_OPT_UID_RESTRICTION, slurm_get_slurmd_user_id()); if (err != EMUNGE_SUCCESS) { error("Unable to set uid restriction on munge credentials: %s", munge_ctx_strerror (ctx)); munge_ctx_destroy(ctx); return(NULL); } return ((void *) ctx); }
/* * Derived from the rcmd() libc call, with modified interface. * Is MT-safe if gethostbyname_r is defined. * Connection can time out. * * ahost (IN) target hostname * port (IN) port to connect to * remuser (IN) remote username * cmd (IN) remote command to execute under shell * fd2p (IN) if non NULL, return stderr file descriptor here * int (RETURN) socket for I/O on success */ int mcmd(char **ahost, int port, char *remuser, char *cmd, int *fd2p, char *munge_socket) { struct sockaddr m_socket; struct sockaddr_in *getp; struct sockaddr_in sin, from; struct sockaddr_storage ss; struct hostent *h_ent = NULL; struct in_addr m_in; unsigned int rand, randl; unsigned int randy = 0; int s, s2, rv, mcount, lport; char c; char num[6] = {0}; char *mptr; char *mbuf; char *tmbuf; char *m; char *mpvers; char num_seq[12] = {0}; socklen_t len; sigset_t blockme; sigset_t oldset; #ifdef HAVE_GETHOSTBYNAME_R_6 struct hostent h_entry; int h_ent_bsize = HBUF_LEN; char h_ent_buf[HBUF_LEN] = {0}; #endif int h_ent_err = 0; unsigned char *hptr; char haddrdot[MAXHOSTNAMELEN + MRSH_LOCALHOST_KEYLEN + 1] = {0}; munge_ctx_t ctx; sigemptyset(&blockme); sigaddset(&blockme, SIGURG); sigaddset(&blockme, SIGPIPE); SET_PTHREAD(); if (fd2p != NULL) { /* * Generate a random number to send in our package to the * server. We will see it again and compare it when the * server sets up the stderr socket and sends it to us. * We need to loop for the tiny possibility we read 0 :P */ int rand_fd; if ((rand_fd = open ("/dev/urandom", O_RDONLY | O_NONBLOCK)) < 0) { perror("mcmd: Open of /dev/urandom failed"); exit(1); } do { if ((rv = read (rand_fd, &randy, sizeof(uint32_t))) < 0) { perror("mcmd: Read of /dev/urandom failed"); close(rand_fd); exit(1); } if (rv < (int) (sizeof(uint32_t))) { perror("mcmd: Read returned too few bytes"); close(rand_fd); exit(1); } } while (randy == 0); close(rand_fd); } /* Convert to decimal string, is 0 if we don't want stderr. */ snprintf(num_seq, sizeof(num_seq),"%d",randy); /* * Start setup of the stdin/stdout socket... */ lport = 0; len = sizeof(struct sockaddr_in); if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("mcmd: socket call stdout failed"); exit(1); } memset (&ss, '\0', sizeof(ss)); ss.ss_family = AF_INET; if (bind(s, (struct sockaddr *)&ss, len) < 0) { perror("mcmd: bind failed"); goto bad; } sin.sin_family = AF_INET; #ifdef HAVE_GETHOSTBYNAME_R_6 (void) gethostbyname_r(*ahost, &h_entry, &h_ent_buf[0], h_ent_bsize, &h_ent, &h_ent_err); #else h_ent = gethostbyname(*ahost); h_ent_err = h_errno; #endif if (h_ent == NULL) { switch (h_ent_err) { case HOST_NOT_FOUND: fprintf(stderr,"mcmd: Hostname not found.\n"); goto bad; case NO_ADDRESS: fprintf(stderr,"mcmd: Can't find IP address.\n"); goto bad; case NO_RECOVERY: fprintf(stderr,"mcmd: A non-recoverable error.\n"); goto bad; case TRY_AGAIN: fprintf(stderr,"mcmd: Error on name server.\n"); goto bad; default: fprintf(stderr,"mcmd: Unknown error.\n"); goto bad; } } memcpy(&sin.sin_addr.s_addr, *h_ent->h_addr_list, h_ent->h_length); sin.sin_port = port; if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("mcmd: connect failed"); goto bad; } /* save address in buffer */ if ((strcmp(*ahost, "localhost") == 0) || (strcmp(*ahost, "127.0.0.1") == 0)) { /* Special case for localhost */ char hostname[MAXHOSTNAMELEN+1]; memset(hostname, '\0', MAXHOSTNAMELEN+1); if (gethostname(hostname, MAXHOSTNAMELEN) < 0) { perror("mcmd: gethostname call failed"); exit(1); } strncpy(haddrdot, MRSH_LOCALHOST_KEY, MRSH_LOCALHOST_KEYLEN); strncat(haddrdot, hostname, MAXHOSTNAMELEN); } else { memcpy(&m_in.s_addr, *h_ent->h_addr_list, h_ent->h_length); hptr = (unsigned char *) &m_in; sprintf(haddrdot, "%u.%u.%u.%u", hptr[0], hptr[1], hptr[2], hptr[3]); } lport = 0; s2 = -1; if (fd2p != NULL) { /* * Start the socket setup for the stderr. */ struct sockaddr_in sin2; if ((s2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("mcmd: socket call for stderr failed"); goto bad; } memset (&sin2, 0, sizeof(sin2)); sin2.sin_family = AF_INET; sin2.sin_addr.s_addr = htonl(INADDR_ANY); sin2.sin_port = 0; if (bind(s2, (struct sockaddr *)&sin2, sizeof(sin2)) < 0) { perror("mcmd: bind failed"); close(s2); goto bad; } len = sizeof(struct sockaddr); /* * Retrieve our port number so we can hand it to the server * for the return (stderr) connection... */ if (getsockname(s2,&m_socket,&len) < 0) { perror("mcmd: getsockname failed"); close(s2); goto bad; } getp = (struct sockaddr_in *)&m_socket; lport = ntohs(getp->sin_port); if (listen(s2, 5) < 0) { perror("mcmd: listen() failed"); close(s2); goto bad; } } /* put port in buffer. will be 0 if user didn't want stderr */ snprintf(num,sizeof(num),"%d",lport); /* * We call munge_encode which will take what we write in and * return a pointer to an munged buffer. What we get back is * a null terminated string of encrypted characters. * * The format of the unmunged buffer is as follows (each a * string terminated with a '\0' (null): * * stderr_port_number & random_number are 0 if user did not * request stderr socket * * SIZE EXAMPLE * ========== ============= * remote_user_name variable "mhaskell" * '\0' * protocol version < 12 bytes "1.2" * '\0' * IP address of requestor [1] 7-15 bytes "134.9.11.155" * '\0' * stderr_port_number 4-8 bytes "50111" * '\0' * random_number 1-8 bytes "1f79ca0e" * '\0' * users_command variable "ls -al" * '\0' '\0' * * (The last extra null is accounted for in the following * line's last strlen() call.) * * [1] - With the exception when 127.0.0.1 or "localhost" are * input by the user. In that situation, the MRSH_LOCALHOST_KEY * and hostname are concatenated and the size may be much larger * than 7-15 bytes. */ mpvers = MRSH_PROTOCOL_VERSION; mcount = ((strlen(remuser)+1) + (strlen(mpvers)+1) + (strlen(haddrdot)+1) + (strlen(num)+1) + (strlen(num_seq)+1) + strlen(cmd)+2); tmbuf = mbuf = malloc(mcount); if (tmbuf == NULL) { perror("mcmd: Error from malloc"); close(s2); goto bad; } /* * The following memset() call takes the extra trailing null * as part of its count as well. */ memset(mbuf,0,mcount); mptr = strcpy(mbuf, remuser); mptr += strlen(remuser)+1; mptr = strcpy(mptr, mpvers); mptr += strlen(mpvers)+1; mptr = strcpy(mptr, haddrdot); mptr += strlen(haddrdot)+1; mptr = strcpy(mptr, num); mptr += strlen(num)+1; mptr = strcpy(mptr, num_seq); mptr += strlen(num_seq)+1; mptr = strcpy(mptr, cmd); if ((ctx = munge_ctx_create()) == NULL) { fprintf(stderr, "munge_ctx_create: %s\n", strerror(errno)); close(s2); free(tmbuf); goto bad; } if (munge_socket) { if ((rv = munge_ctx_set (ctx, MUNGE_OPT_SOCKET, munge_socket)) != EMUNGE_SUCCESS) { fprintf(stderr,"munge_ctx_set: %s\n", munge_ctx_strerror(ctx)); munge_ctx_destroy(ctx); close(s2); free(tmbuf); goto bad; } } if ((rv = munge_encode(&m,ctx,mbuf,mcount)) != EMUNGE_SUCCESS) { fprintf(stderr,"munge_encode: %s\n", munge_ctx_strerror(ctx)); munge_ctx_destroy(ctx); close(s2); free(tmbuf); goto bad; } munge_ctx_destroy(ctx); mcount = (strlen(m)+1); /* * Write stderr port in the clear in case we can't decode for * some reason (i.e. bad credentials). May be 0 if user * doesn't want stderr. */ if (fd2p != NULL) { rv = fd_write_n(s, num, strlen(num)+1); if (rv != (ssize_t)(strlen(num)+1)) { free(m); free(tmbuf); if (rv == -1) { if (errno == EPIPE) perror("mcmd: Lost connection (EPIPE)"); else perror("mcmd: Write of stderr port"); } else fprintf(stderr, "mcmd: write incorrect number of bytes.\n"); close(s2); goto bad; } } else { write(s, "", 1); lport = 0; } /* * Write the munge_encoded blob to the socket. */ if ((rv = fd_write_n(s, m, mcount)) != mcount) { free(m); free(tmbuf); if (rv == -1) { if (errno == EPIPE) perror("mcmd: Lost connection (EPIPE)"); else perror("mcmd: Write of munge data"); } else fprintf(stderr, "mcmd: write incorrect number of bytes.\n"); close(s2); goto bad; } free(m); free(tmbuf); if (fd2p != NULL) { /* * Wait for stderr connection from daemon. */ int maxfd, s3; fd_set reads; errno = 0; FD_ZERO(&reads); FD_SET(s, &reads); FD_SET(s2, &reads); maxfd = (s > s2) ? s : s2; if (select(maxfd + 1, &reads, 0, 0, 0) < 1 || !FD_ISSET(s2, &reads)) { if (errno != 0) perror("mcmd: Select failed (setting up stderr)"); else { char buf[100]; int rv = read(s, buf, 100); if (rv == 0) fprintf(stderr, "mcmd: Connection closed by remote host.\n"); else if (rv > 0) fprintf(stderr, "mcmd: Protocol failure in circuit setup.\n"); else /* rv < 0 */ fprintf(stderr, "mcmd: %s\n", strerror(errno)); } close(s2); goto bad; } errno = 0; len = sizeof(from); /* arg to accept */ if ((s3 = accept(s2, (struct sockaddr *)&from, &len)) < 0) { perror("mcmd: accept (stderr) failed"); close(s2); goto bad; } if (from.sin_family != AF_INET) { fprintf(stderr, "mcmd: bad family type: %d\n", from.sin_family); goto bad2; } close(s2); /* * The following fixes a race condition between the daemon * and the client. The daemon is waiting for a null to * proceed. We do this to make sure that we have our * socket is up prior to the daemon running the command. */ if (write(s,"",1) != 1) { perror("mcmd: Could not communicate to daemon to proceed"); close(s3); goto bad; } /* * Read from our stderr. The server should have placed * our random number we generated onto this socket. */ rv = fd_read_n(s3, &rand, sizeof(rand)); if (rv <= 0) { if (rv == 0) perror("mcmd: Connection closed by remote host"); else perror("mcmd: Bad read of verification number"); close(s3); goto bad; } randl = ntohl(rand); if (randl != randy) { char tmpbuf[LINEBUFSIZE] = {0}; char *tptr = &tmpbuf[0]; memcpy(tptr,(char *) &rand,sizeof(rand)); tptr += sizeof(rand); if ((fd_read_line (s3, tptr, LINEBUFSIZE - sizeof(rand))) < 0) { perror("mcmd: Read error from remote host"); close(s3); goto bad; } /* Legacy rsh may consider the first byte an error code, * so don't output this byte. */ if (tmpbuf[0] == '\01') tptr = &tmpbuf[1]; else tptr = &tmpbuf[0]; fprintf(stderr,"mcmd error returned: %s\n", tptr); close(s3); goto bad; } /* * Set the stderr file descriptor for the user... */ *fd2p = s3; } if ((rv = read(s, &c, 1)) < 0) { perror("mcmd: read: protocol failure"); goto bad2; } if (rv != 1) { fprintf(stderr, "mcmd: read: protocol failure: invalid response.\n"); goto bad2; } if (c != '\0') { /* retrieve error string from remote server */ char tmpbuf[LINEBUFSIZE]; if (fd_read_line (s, &tmpbuf[0], LINEBUFSIZE ) < 0) { perror("mcmd: Error from remote host"); goto bad2; } fprintf(stderr,"mcmd error returned: %s\n",&tmpbuf[0]); goto bad2; } RESTORE_PTHREAD(); return (s); bad2: if (lport) close(*fd2p); bad: close(s); RESTORE_PTHREAD(); exit(1); }
int PBSD_munge_authenticate( int psock, /* I */ int handle) /* I */ { int rc; munge_ctx_t mctx = NULL; munge_err_t mret = EMUNGE_SNAFU; char *mcred = NULL; /* user id and name stuff */ struct passwd *pwent; uid_t myrealuid; struct batch_reply *reply; unsigned short user_port = 0; struct sockaddr_in sockname; socklen_t socknamelen = sizeof(sockname); struct tcp_chan *chan; if ((mctx = munge_ctx_create()) == NULL) { return(-1); } if ((mret = munge_encode (&mcred, mctx, NULL, 0)) != EMUNGE_SUCCESS) { const char *merrmsg = NULL; if (!(merrmsg = munge_ctx_strerror(mctx))) { merrmsg = munge_strerror(mret); } fprintf(stderr, "munge_encode failed: %s (%d)\n", merrmsg, mret); munge_ctx_destroy(mctx); return(PBSE_MUNGE_NOT_FOUND); /*TODO more fine-grained error codes? */ } munge_ctx_destroy(mctx); /* We got the certificate. Now make the PBS_BATCH_AltAuthenUser request */ myrealuid = getuid(); pwent = getpwuid(myrealuid); rc = getsockname(psock, (struct sockaddr *)&sockname, &socknamelen); if (rc == -1) { fprintf(stderr, "getsockname failed: %d\n", errno); return(-1); } user_port = ntohs(sockname.sin_port); if ((chan = DIS_tcp_setup(psock)) == NULL) { } else if ((rc = encode_DIS_ReqHdr(chan, PBS_BATCH_AltAuthenUser, pwent->pw_name)) || (rc = diswui(chan, user_port)) || (rc = diswst(chan, mcred)) || (rc = encode_DIS_ReqExtend(chan, NULL)) || (rc = DIS_tcp_wflush(chan))) { PBSD_munge_cred_destroy(&mcred); /* ERROR */ return(rc); } else { int local_err = PBSE_NONE; PBSD_munge_cred_destroy(&mcred); /* read the reply */ if ((reply = PBSD_rdrpy(&local_err, handle)) != NULL) free(reply); return(PBSE_NONE); } return(-1); }
/* * Derived from the mcmd() libc call, with modified interface. * This version is MT-safe. Errors are displayed in pdsh-compat format. * Connection can time out. * ahost (IN) target hostname * addr (IN) 4 byte internet address * locuser (IN) local username * remuser (IN) remote username * cmd (IN) remote command to execute under shell * rank (IN) not used * fd2p (IN) if non NULL, return stderr file descriptor here * int (RETURN) -1 on error, socket for I/O on success * * Originally by Mike Haskell for mrsh, modified slightly to work with pdsh by: * - making mcmd always thread safe * - using "err" function output errors. * - passing in address as addr intead of calling gethostbyname * - using default mshell port instead of calling getservbyname * */ static int mcmd(char *ahost, char *addr, char *locuser, char *remuser, char *cmd, int rank, int *fd2p, void **argp) { struct sockaddr m_socket; struct sockaddr_in *getp; struct sockaddr_in sin, from; struct sockaddr_storage ss; struct in_addr m_in; unsigned int rand, randl; unsigned char *hptr; int s, s2, rv, mcount, lport; char c; char num[6] = {0}; char *mptr; char *mbuf; char *tmbuf; char *m; char *mpvers; char num_seq[12] = {0}; socklen_t len; sigset_t blockme; sigset_t oldset; char haddrdot[MAXHOSTNAMELEN + MRSH_LOCALHOST_KEYLEN + 1] = {0}; munge_ctx_t ctx; struct xpollfd xpfds[2]; memset (xpfds, 0, sizeof (xpfds)); memset (&sin, 0, sizeof (sin)); sigemptyset(&blockme); sigaddset(&blockme, SIGURG); sigaddset(&blockme, SIGPIPE); SET_PTHREAD(); /* Convert randy to decimal string, 0 if we dont' want stderr */ if (fd2p != NULL) snprintf(num_seq, sizeof(num_seq),"%d",randy); else snprintf(num_seq, sizeof(num_seq),"%d",0); /* * Start setup of the stdin/stdout socket... */ lport = 0; len = sizeof(struct sockaddr_in); if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { err("%p: %S: mcmd: socket call stdout failed: %m\n", ahost); EXIT_PTHREAD(); } memset (&ss, '\0', sizeof(ss)); ss.ss_family = AF_INET; if (bind(s, (struct sockaddr *)&ss, len) < 0) { err("%p: %S: mcmd: bind failed: %m\n", ahost); goto bad; } sin.sin_family = AF_INET; memcpy(&sin.sin_addr.s_addr, addr, IP_ADDR_LEN); sin.sin_port = htons(MRSH_PORT); if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { err("%p: %S: mcmd: connect failed: %m\n", ahost); goto bad; } lport = 0; s2 = -1; if (fd2p != NULL) { /* * Start the socket setup for the stderr. */ struct sockaddr_in sin2; if ((s2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) { err("%p: %S: mcmd: socket call for stderr failed: %m\n", ahost); goto bad; } memset (&sin2, 0, sizeof(sin2)); sin2.sin_family = AF_INET; sin2.sin_addr.s_addr = htonl(INADDR_ANY); sin2.sin_port = 0; if (bind(s2,(struct sockaddr *)&sin2, sizeof(sin2)) < 0) { err("%p: %S: mcmd: bind failed: %m\n", ahost); close(s2); goto bad; } len = sizeof(struct sockaddr); /* * Retrieve our port number so we can hand it to the server * for the return (stderr) connection... */ /* getsockname is thread safe */ if (getsockname(s2,&m_socket,&len) < 0) { err("%p: %S: mcmd: getsockname failed: %m\n", ahost); close(s2); goto bad; } getp = (struct sockaddr_in *)&m_socket; lport = ntohs(getp->sin_port); if (listen(s2, 5) < 0) { err("%p: %S: mcmd: listen() failed: %m\n", ahost); close(s2); goto bad; } } /* put port in buffer. will be 0 if user didn't want stderr */ snprintf(num,sizeof(num),"%d",lport); /* * Use special keyed string if target is localhost, otherwise, * encode the IP addr string. */ if (!encode_localhost_string (ahost, haddrdot, sizeof (haddrdot))) { /* inet_ntoa is not thread safe, so we use the following, * which is more or less ripped from glibc */ memcpy(&m_in.s_addr, addr, IP_ADDR_LEN); hptr = (unsigned char *)&m_in; sprintf(haddrdot, "%u.%u.%u.%u", hptr[0], hptr[1], hptr[2], hptr[3]); } /* * We call munge_encode which will take what we write in and return a * pointer to an munged buffer. What we get back is a null terminated * string of encrypted characters. * * The format of the unmunged buffer is as follows (each a string terminated * with a '\0' (null): * * stderr_port_number & /dev/urandom_client_produce_number are 0 * if user did not request stderr socket * SIZE EXAMPLE * ========== ============= * remote_user_name variable "mhaskell" * '\0' * protocol version < 12 bytes "1.2" * '\0' * dotted_decimal_address_of_this_server 7-15 bytes "134.9.11.155" * '\0' * stderr_port_number 4-8 bytes "50111" * '\0' * /dev/urandom_client_produced_number 1-8 bytes "1f79ca0e" * '\0' * users_command variable "ls -al" * '\0' '\0' * * (The last extra null is accounted for in the following line's * last strlen() call.) * */ mpvers = MRSH_PROTOCOL_VERSION; mcount = ((strlen(remuser)+1) + (strlen(mpvers)+1) + (strlen(haddrdot)+1) + (strlen(num)+1) + (strlen(num_seq)+1) + strlen(cmd)+2); tmbuf = mbuf = malloc(mcount); if (tmbuf == NULL) { err("%p: %S: mcmd: Error from malloc\n", ahost); close(s2); goto bad; } /* * The following memset() call takes the extra trailing null as * part of its count as well. */ memset(mbuf,0,mcount); mptr = strcpy(mbuf, remuser); mptr += strlen(remuser)+1; mptr = strcpy(mptr, mpvers); mptr += strlen(mpvers)+1; mptr = strcpy(mptr, haddrdot); mptr += strlen(haddrdot)+1; mptr = strcpy(mptr, num); mptr += strlen(num)+1; mptr = strcpy(mptr, num_seq); mptr += strlen(num_seq)+1; mptr = strcpy(mptr, cmd); ctx = munge_ctx_create(); if ((rv = munge_encode(&m,ctx,mbuf,mcount)) != EMUNGE_SUCCESS) { err("%p: %S: mcmd: munge_encode: %s\n", ahost, munge_ctx_strerror(ctx)); munge_ctx_destroy(ctx); if (s2 >= 0) close(s2); free(tmbuf); goto bad; } munge_ctx_destroy(ctx); mcount = (strlen(m)+1); /* * Write stderr port in the clear in case we can't decode for * some reason (i.e. bad credentials). May be 0 if user * doesn't want stderr */ if (fd2p != NULL) { rv = fd_write_n(s, num, strlen(num)+1); if (rv != (strlen(num) + 1)) { free(m); free(tmbuf); if (errno == EPIPE) err("%p: %S: mcmd: Lost connection (EPIPE): %m", ahost); else err("%p: %S: mcmd: Write of stderr port failed: %m\n", ahost); close(s2); goto bad; } } else { write(s, "", 1); lport = 0; } /* * Write the munge_encoded blob to the socket. */ rv = fd_write_n(s, m, mcount); if (rv != mcount) { free(m); free(tmbuf); if (errno == EPIPE) err("%p: %S: mcmd: Lost connection: %m\n", ahost); else err("%p: %S: mcmd: Write to socket failed: %m\n", ahost); close(s2); goto bad; } free(m); free(tmbuf); if (fd2p != NULL) { /* * Wait for stderr connection from daemon. */ int s3; errno = 0; xpfds[0].fd = s; xpfds[1].fd = s2; xpfds[0].events = xpfds[1].events = XPOLLREAD; if ( ((rv = xpoll(xpfds, 2, -1)) < 0) || rv != 1 || (xpfds[0].revents > 0)) { if (errno != 0) err("%p: %S: mcmd: xpoll (setting up stderr): %m\n", ahost); else err("%p: %S: mcmd: xpoll: protocol failure in circuit setup\n", ahost); (void) close(s2); goto bad; } errno = 0; len = sizeof(from); /* arg to accept */ if ((s3 = accept(s2, (struct sockaddr *)&from, &len)) < 0) { err("%p: %S: mcmd: accept (stderr) failed: %m\n", ahost); close(s2); goto bad; } if (from.sin_family != AF_INET) { err("%p: %S: mcmd: bad family type: %d\n", ahost, from.sin_family); goto bad2; } close(s2); /* * The following fixes a race condition between the daemon * and the client. The daemon is waiting for a null to * proceed. We do this to make sure that we have our * socket is up prior to the daemon running the command. */ if (write(s,"",1) < 0) { err("%p: %S: mcmd: Could not communicate to daemon to proceed: %m\n", ahost); close(s3); goto bad; } /* * Read from our stderr. The server should have placed our * random number we generated onto this socket. */ rv = fd_read_n(s3, &rand, sizeof(rand)); if (rv != (ssize_t) (sizeof(rand))) { err("%p: %S: mcmd: Bad read of expected verification " "number off of stderr socket: %m\n", ahost); close(s3); goto bad; } randl = ntohl(rand); if (randl != randy) { char tmpbuf[LINEBUFSIZE] = {0}; char *tptr = &tmpbuf[0]; memcpy(tptr,(char *) &rand,sizeof(rand)); tptr += sizeof(rand); if (fd_read_line (s3, tptr, LINEBUFSIZE) < 0) err("%p: %S: mcmd: Read error from remote host: %m\n", ahost); else err("%p: %S: mcmd: Error: %s\n", ahost, &tmpbuf[0]); close(s3); goto bad; } /* * Set the stderr file descriptor for the user... */ *fd2p = s3; } if ((rv = read(s, &c, 1)) < 0) { err("%p: %S: mcmd: read: protocol failure: %m\n", ahost); goto bad2; } if (rv != 1) { err("%p: %S: mcmd: read: protocol failure: invalid response\n", ahost); goto bad2; } if (c != '\0') { /* retrieve error string from remote server */ char tmpbuf[LINEBUFSIZE]; if (fd_read_line (s, &tmpbuf[0], LINEBUFSIZE) < 0) err("%p: %S: mcmd: Error from remote host\n", ahost); else err("%p: %S: mcmd: Error: %s\n", ahost, tmpbuf); goto bad2; } RESTORE_PTHREAD(); return (s); bad2: if (lport) close(*fd2p); bad: close(s); EXIT_PTHREAD(); }
/* * Decode the munge encoded credential `m_str' placing results, if validated, * into slurm credential `c' */ static int _decode_cred(slurm_auth_credential_t *c, char *socket) { int retry = 2; munge_err_t e; munge_ctx_t ctx; if (c == NULL) return SLURM_ERROR; xassert(c->magic == MUNGE_MAGIC); if (c->verified) return SLURM_SUCCESS; if ((ctx = munge_ctx_create()) == NULL) { error("munge_ctx_create failure"); return SLURM_ERROR; } if (socket && (munge_ctx_set(ctx, MUNGE_OPT_SOCKET, socket) != EMUNGE_SUCCESS)) { error("munge_ctx_set failure"); munge_ctx_destroy(ctx); return SLURM_ERROR; } again: c->buf = NULL; e = munge_decode(c->m_str, ctx, &c->buf, &c->len, &c->uid, &c->gid); if (e != EMUNGE_SUCCESS) { if (c->buf) { free(c->buf); c->buf = NULL; } if ((e == EMUNGE_SOCKET) && retry--) { error ("Munge decode failed: %s (retrying ...)", munge_ctx_strerror(ctx)); #ifdef MULTIPLE_SLURMD sleep(1); #endif goto again; } #ifdef MULTIPLE_SLURMD /* In multple slurmd mode this will happen all the * time since we are authenticating with the same * munged. */ if (e != EMUNGE_CRED_REPLAYED) { #endif /* * Print any valid credential data */ error ("Munge decode failed: %s", munge_ctx_strerror(ctx)); _print_cred(ctx); if (e == EMUNGE_CRED_REWOUND) error("Check for out of sync clocks"); c->cr_errno = e + MUNGE_ERRNO_OFFSET; #ifdef MULTIPLE_SLURMD } else { debug2("We had a replayed cred, " "but this is expected in multiple " "slurmd mode."); e = 0; } #endif goto done; } c->verified = true; done: munge_ctx_destroy(ctx); return e ? SLURM_ERROR : SLURM_SUCCESS; }
/* * Allocate a credential. This function should return NULL if it cannot * allocate a credential. Whether the credential is populated with useful * data at this time is implementation-dependent. */ slurm_auth_credential_t * slurm_auth_create( void *argv[], char *socket ) { int retry = 2; slurm_auth_credential_t *cred = NULL; munge_err_t e = EMUNGE_SUCCESS; munge_ctx_t ctx = munge_ctx_create(); SigFunc *ohandler; if (ctx == NULL) { error("munge_ctx_create failure"); return NULL; } #if 0 /* This logic can be used to determine what socket is used by default. * A typical name is "/var/run/munge/munge.socket.2" */ { char *old_socket; if (munge_ctx_get(ctx, MUNGE_OPT_SOCKET, &old_socket) != EMUNGE_SUCCESS) error("munge_ctx_get failure"); else info("Default Munge socket is %s", old_socket); } #endif if (socket && (munge_ctx_set(ctx, MUNGE_OPT_SOCKET, socket) != EMUNGE_SUCCESS)) { error("munge_ctx_set failure"); munge_ctx_destroy(ctx); return NULL; } cred = xmalloc(sizeof(*cred)); cred->verified = false; cred->m_str = NULL; cred->buf = NULL; cred->len = 0; cred->cr_errno = SLURM_SUCCESS; xassert(cred->magic = MUNGE_MAGIC); /* * Temporarily block SIGALARM to avoid misleading * "Munged communication error" from libmunge if we * happen to time out the connection in this secion of * code. */ ohandler = xsignal(SIGALRM, SIG_BLOCK); again: e = munge_encode(&cred->m_str, ctx, cred->buf, cred->len); if (e != EMUNGE_SUCCESS) { if ((e == EMUNGE_SOCKET) && retry--) { error ("Munge encode failed: %s (retrying ...)", munge_ctx_strerror(ctx)); #ifdef MULTIPLE_SLURMD sleep(1); #endif goto again; } error("Munge encode failed: %s", munge_ctx_strerror(ctx)); xfree( cred ); cred = NULL; plugin_errno = e + MUNGE_ERRNO_OFFSET; } xsignal(SIGALRM, ohandler); munge_ctx_destroy(ctx); return cred; }
void parse_cmdline (conf_t conf, int argc, char **argv) { /* Parses the command-line, altering the configuration [conf] as specified. */ char *prog; int c; char *p; int i; long int l; unsigned long u; int multiplier; munge_err_t e; opterr = 0; /* suppress default getopt err msgs */ prog = (prog = strrchr (argv[0], '/')) ? prog + 1 : argv[0]; for (;;) { c = getopt_long (argc, argv, short_opts, long_opts, NULL); if (c == -1) { /* reached end of option list */ break; } switch (c) { case 'h': display_help (prog); exit (EMUNGE_SUCCESS); break; case 'L': display_license (); exit (EMUNGE_SUCCESS); break; case 'V': display_version (); exit (EMUNGE_SUCCESS); break; case 'q': g_got_quiet = 1; break; case 'c': i = munge_enum_str_to_int (MUNGE_ENUM_CIPHER, optarg); if ((i < 0) || !munge_enum_is_valid (MUNGE_ENUM_CIPHER, i)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid cipher type \"%s\"", optarg); } e = munge_ctx_set (conf->ctx, MUNGE_OPT_CIPHER_TYPE, i); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set cipher type: %s", munge_ctx_strerror (conf->ctx)); } break; case 'C': display_strings ("Cipher types", MUNGE_ENUM_CIPHER); exit (EMUNGE_SUCCESS); break; case 'm': i = munge_enum_str_to_int (MUNGE_ENUM_MAC, optarg); if ((i < 0) || !munge_enum_is_valid (MUNGE_ENUM_MAC, i)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid MAC type \"%s\"", optarg); } e = munge_ctx_set (conf->ctx, MUNGE_OPT_MAC_TYPE, i); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set MAC type: %s", munge_ctx_strerror (conf->ctx)); } break; case 'M': display_strings ("MAC types", MUNGE_ENUM_MAC); exit (EMUNGE_SUCCESS); break; case 'z': i = munge_enum_str_to_int (MUNGE_ENUM_ZIP, optarg); if ((i < 0) || !munge_enum_is_valid (MUNGE_ENUM_ZIP, i)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid compression type \"%s\"", optarg); } e = munge_ctx_set (conf->ctx, MUNGE_OPT_ZIP_TYPE, i); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set compression type: %s", munge_ctx_strerror (conf->ctx)); } break; case 'Z': display_strings ("Compression types", MUNGE_ENUM_ZIP); exit (EMUNGE_SUCCESS); break; case 'e': conf->do_decode = 0; break; case 'd': conf->do_decode = 1; break; case 'l': errno = 0; l = strtol (optarg, &p, 10); if ((optarg == p) || ((*p != '\0') && (*(p+1) != '\0')) || (l < 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid number of bytes '%s'", optarg); } if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum number of %d bytes", INT_MAX); } if (!(multiplier = get_si_multiple (*p))) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid number specifier '%c'", *p); } if (l > (INT_MAX / multiplier)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum number of %d bytes", INT_MAX); } conf->num_payload = (int) (l * multiplier); break; case 'u': if (query_uid (optarg, (uid_t *) &i) < 0) { log_err (EMUNGE_SNAFU, LOG_ERR, "Unrecognized user \"%s\"", optarg); } e = munge_ctx_set (conf->ctx, MUNGE_OPT_UID_RESTRICTION, i); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set UID restriction: %s", munge_ctx_strerror (conf->ctx)); } break; case 'g': if (query_gid (optarg, (gid_t *) &i) < 0) { log_err (EMUNGE_SNAFU, LOG_ERR, "Unrecognized group \"%s\"", optarg); } e = munge_ctx_set (conf->ctx, MUNGE_OPT_GID_RESTRICTION, i); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set GID restriction: %s", munge_ctx_strerror (conf->ctx)); } break; case 't': errno = 0; l = strtol (optarg, &p, 10); if ((optarg == p) || (*p != '\0') || (l < -1)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid time-to-live '%s'", optarg); } if ((errno == ERANGE) && (l == LONG_MAX)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Overflowed maximum time-to-live of %ld seconds", LONG_MAX); } if (l > UINT_MAX) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum time-to-live of %u seconds", UINT_MAX); } if (l == -1) { l = MUNGE_TTL_MAXIMUM; } e = munge_ctx_set (conf->ctx, MUNGE_OPT_TTL, (int) l); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set time-to-live: %s", munge_ctx_strerror (conf->ctx)); } break; case 'S': e = munge_ctx_set (conf->ctx, MUNGE_OPT_SOCKET, optarg); if (e != EMUNGE_SUCCESS) { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to set munge socket name: %s", munge_ctx_strerror (conf->ctx)); } break; case 'D': errno = 0; l = strtol (optarg, &p, 10); if ((optarg == p) || ((*p != '\0') && (*(p+1) != '\0')) || (l <= 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid duration '%s'", optarg); } if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum duration of %d seconds", INT_MAX); } if (!(multiplier = get_time_multiple (*p))) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid duration specifier '%c'", *p); } if (l > (INT_MAX / multiplier)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum duration of %d seconds", INT_MAX); } conf->num_seconds = (int) (l * multiplier); break; case 'N': errno = 0; u = strtoul (optarg, &p, 10); if ((optarg == p) || ((*p != '\0') && (*(p+1) != '\0')) || (u == 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid number of credentials '%s'", optarg); } if ((errno == ERANGE) && (u == ULONG_MAX)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum number of %lu credentials", ULONG_MAX); } if (!(multiplier = get_si_multiple (*p))) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid number specifier '%c'", *p); } if (u > (ULONG_MAX / multiplier)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum number of %lu credentials", ULONG_MAX); } conf->num_creds = u * multiplier; break; case 'T': errno = 0; l = strtol (optarg, &p, 10); if ((optarg == p) || (*p != '\0') || (l <= 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid number of threads '%s'", optarg); } if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX) || (l > conf->max_threads)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum number of %d thread%s", conf->max_threads, (conf->max_threads == 1) ? "" : "s"); } conf->num_threads = (int) l; break; case 'W': errno = 0; l = strtol (optarg, &p, 10); if ((optarg == p) || (*p != '\0') || (l <= 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid number of seconds '%s'", optarg); } if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Exceeded maximum number of %d seconds", INT_MAX); } conf->warn_time = (int) l; break; case '?': if (optopt > 0) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid option \"-%c\"", optopt); } else if (optind > 1) { log_err (EMUNGE_SNAFU, LOG_ERR, "Invalid option \"%s\"", argv[optind - 1]); } else { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to process command-line"); } break; case ':': if ((optind > 1) && (strncmp (argv[optind - 1], "--", 2) == 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Missing argument for option \"%s\"", argv[optind - 1]); } else if (optopt > 0) { log_err (EMUNGE_SNAFU, LOG_ERR, "Missing argument for option \"-%c\"", optopt); } else { log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to process command-line"); } break; default: if ((optind > 1) && (strncmp (argv[optind - 1], "--", 2) == 0)) { log_err (EMUNGE_SNAFU, LOG_ERR, "Unimplemented option \"%s\"", argv[optind - 1]); } else { log_err (EMUNGE_SNAFU, LOG_ERR, "Unimplemented option \"-%c\"", c); } break; } } if (argv[optind]) { log_err (EMUNGE_SNAFU, LOG_ERR, "Unrecognized parameter \"%s\"", argv[optind]); } /* Create arbitrary payload of the specified length. */ if (conf->num_payload > 0) { if (!(conf->payload = malloc (conf->num_payload + 1))) { log_err (EMUNGE_NO_MEMORY, LOG_ERR, "Failed to allocate credential payload of %d byte%s", conf->num_payload, (conf->num_payload == 1 ? "" : "s")); } for (i = 0, c = 'A'; i < conf->num_payload; i++) { if ((conf->payload[i] = c++) == 'Z') { c = 'A'; } } conf->payload[conf->num_payload] = '\0'; } return; }
void * remunge (conf_t conf) { /* Worker thread responsible for encoding/decoding/validating credentials. */ tdata_t tdata; int cancel_state; unsigned long n; unsigned long got_encode_err; unsigned long got_decode_err; struct timeval t_start; struct timeval t_stop; double delta; munge_err_t e; char *cred; void *data; int dlen; uid_t uid; gid_t gid; tdata = create_tdata (conf); pthread_cleanup_push ((thread_cleanup_f) remunge_cleanup, tdata); if ((errno = pthread_mutex_lock (&conf->mutex)) != 0) { log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock mutex"); } while (conf->num_creds - conf->shared.num_creds_done > 0) { pthread_testcancel (); if ((errno = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &cancel_state)) != 0) { log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to disable thread cancellation"); } n = ++conf->shared.num_creds_done; if ((errno = pthread_mutex_unlock (&conf->mutex)) != 0) { log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock mutex"); } got_encode_err = 0; got_decode_err = 0; data = NULL; GET_TIMEVAL (t_start); e = munge_encode(&cred, tdata->ectx, conf->payload, conf->num_payload); GET_TIMEVAL (t_stop); delta = DIFF_TIMEVAL (t_stop, t_start); if (delta > conf->warn_time) { output_msg ("Credential #%lu encoding took %0.3f seconds", n, delta); } if (e != EMUNGE_SUCCESS) { output_msg ("Credential #%lu encoding failed: %s (err=%d)", n, munge_ctx_strerror (tdata->ectx), e); ++got_encode_err; } else if (conf->do_decode) { GET_TIMEVAL (t_start); e = munge_decode (cred, tdata->dctx, &data, &dlen, &uid, &gid); GET_TIMEVAL (t_stop); delta = DIFF_TIMEVAL (t_stop, t_start); if (delta > conf->warn_time) { output_msg ("Credential #%lu decoding took %0.3f seconds", n, delta); } if (e != EMUNGE_SUCCESS) { output_msg ("Credential #%lu decoding failed: %s (err=%d)", n, munge_ctx_strerror (tdata->dctx), e); ++got_decode_err; } /* FIXME: * The following block does some validating of the decoded credential. * It should have a cmdline option to enable this validation check. * The decode ctx should also be checked against the encode ctx. * This becomes slightly more difficult in that it must also take * into account the default field settings. * * This block should be moved into a separate function (or more). * The [cred], [data], [dlen], [uid], and [gid] vars could be placed * into the tdata struct to facilitate parameter passing. */ #if 0 else if (conf->do_validate) { if (getuid () != uid) { output_msg ( "Credential #%lu UID %d does not match process UID %d", n, uid, getuid ()); } if (getgid () != gid) { output_msg ( "Credential #%lu GID %d does not match process GID %d", n, gid, getgid ()); } if (conf->num_payload != dlen) { output_msg ( "Credential #%lu payload length mismatch (%d/%d)", n, conf->num_payload, dlen); } else if (data && memcmp (conf->payload, data, dlen) != 0) { output_msg ("Credential #%lu payload mismatch", n); } } #endif /* 0 */ /* The 'data' parm can still be set on certain munge errors. */ if (data != NULL) { free (data); } } if (cred != NULL) { free (cred); } if ((errno = pthread_setcancelstate (cancel_state, &cancel_state)) != 0) { log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to enable thread cancellation"); } if ((errno = pthread_mutex_lock (&conf->mutex)) != 0) { log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock mutex"); } conf->shared.num_encode_errs += got_encode_err; conf->shared.num_decode_errs += got_decode_err; } pthread_cleanup_pop (1); return (NULL); }