/* klog.c: description * * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Based on code: * * Copyright (c) 1995 - 2000 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define _XOPEN_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #define KA_SERVICE 731 #define KA_TICKET_GRANTING_SERVICE 732 #define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0) struct sockaddr_rxrpc { sa_family_t srx_family; /* address family */ unsigned short srx_service; /* service desired */ unsigned short transport_type; /* type of transport socket (SOCK_DGRAM) */ unsigned short transport_len; /* length of transport address */ union { sa_family_t family; /* transport address family */ struct sockaddr_in sin; /* IPv4 transport address */ struct sockaddr_in6 sin6; /* IPv6 transport address */ } transport; }; #define AF_RXRPC 33 #define PF_RXRPC AF_RXRPC #define SOL_RXRPC 272 #define RXRPC_USER_CALL_ID 1 /* User call ID specifier */ #define RXRPC_ABORT 2 /* Abort request / notification */ #define RXRPC_ACK 3 /* [Server] RPC op final ACK received */ #define RXRPC_RESPONSE 4 /* [Server] security response received */ #define RXRPC_NET_ERROR 5 /* network error received */ #define RXRPC_BUSY 6 /* server busy received */ #define RXRPC_LOCAL_ERROR 7 /* local error generated */ #define RXRPC_PREPARE_CALL_SLOT 8 /* Propose user call ID specifier for next call */ #define RXRPC_SECURITY_KEY 1 /* [clnt] set client security key */ #define RXRPC_SECURITY_KEYRING 2 /* [srvr] set ring of server security keys */ #define RXRPC_EXCLUSIVE_CONNECTION 3 /* [clnt] use exclusive RxRPC connection */ #define RXRPC_MIN_SECURITY_LEVEL 4 /* minimum security level */ #define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0) static const unsigned char local_addr[4] = { 0, 0, 0, 0 }; static const unsigned char remote_addr[4] = { 172, 16, 18, 91 }; #define RXRPC_ADD_CALLID(control, ctrllen, id) \ do { \ struct cmsghdr *__cmsg; \ __cmsg = (void *)(control) + (ctrllen); \ __cmsg->cmsg_len = CMSG_LEN(sizeof(unsigned long)); \ __cmsg->cmsg_level = SOL_RXRPC; \ __cmsg->cmsg_type = RXRPC_USER_CALL_ID; \ *(unsigned long *)CMSG_DATA(__cmsg) = (id); \ (ctrllen) += __cmsg->cmsg_len; \ \ } while (0) #define RXRPC_ADD_ABORT(control, ctrllen, abort_code) \ do { \ struct cmsghdr *__cmsg; \ __cmsg = (void *)(control) + (ctrllen); \ __cmsg->cmsg_len = CMSG_LEN(sizeof(unsigned long)); \ __cmsg->cmsg_level = SOL_RXRPC; \ __cmsg->cmsg_type = RXRPC_ABORT; \ *(unsigned long *)CMSG_DATA(__cmsg) = (abort_code); \ (ctrllen) += __cmsg->cmsg_len; \ \ } while (0) const char KA_GETTGT_REQ_LABEL[] = "gTGS"; const char KA_GETTGT_RPL_LABEL[] = "tgsT"; const char KA_GETTKT_RPL_LABEL[] = "gtkt"; #define KAA_Authenticate 21 #define KAA_AuthenticateV2 22 #define KAT_GetToken 23 struct ka_ticket { des_cblock session_key; time_t end_time; int kvno; int ticket_len; const char *name; const char *instance; const char *cell; const char *server_name; const char *server_instance; const void *ticket; char _reply[0]; }; struct rxrpc_key_sec2_v1 { uint32_t kver; /* key payload interface version */ uint16_t security_index; /* RxRPC header security index */ uint16_t ticket_length; /* length of ticket[] */ uint32_t expiry; /* time at which expires */ uint32_t kvno; /* key version number */ uint8_t session_key[8]; /* DES session key */ uint8_t ticket[0]; /* the encrypted ticket */ }; /*****************************************************************************/ /* * dump the control messages */ static void dump_cmsg(struct msghdr *msg) { struct cmsghdr *cmsg; unsigned long user_id; unsigned char *p; int abort_code; int n; for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { n = cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg)); p = CMSG_DATA(cmsg); printf("CMSG: %zu: ", cmsg->cmsg_len); if (cmsg->cmsg_level == SOL_RXRPC) { switch (cmsg->cmsg_type) { case RXRPC_USER_CALL_ID: printf("RXRPC_USER_CALL_ID: "); if (n != sizeof(user_id)) goto dump_data; memcpy(&user_id, p, sizeof(user_id)); printf("%lx\n", user_id); continue; case RXRPC_ABORT: printf("RXRPC_ABORT: "); if (n != sizeof(abort_code)) goto dump_data; memcpy(&abort_code, p, sizeof(abort_code)); printf("%d\n", abort_code); continue; case RXRPC_ACK: printf("RXRPC_ACK"); if (n != 0) goto dump_data_colon; goto print_nl; case RXRPC_RESPONSE: printf("RXRPC_RESPONSE"); if (n != 0) goto dump_data_colon; goto print_nl; case RXRPC_NET_ERROR: printf("RXRPC_NET_ERROR: "); if (n != sizeof(abort_code)) goto dump_data; memcpy(&abort_code, p, sizeof(abort_code)); printf("%s\n", strerror(abort_code)); continue; case RXRPC_BUSY: printf("RXRPC_BUSY"); if (n != 0) goto dump_data_colon; goto print_nl; case RXRPC_LOCAL_ERROR: printf("RXRPC_LOCAL_ERROR: "); if (n != sizeof(abort_code)) goto dump_data; memcpy(&abort_code, p, sizeof(abort_code)); printf("%s\n", strerror(abort_code)); continue; default: break; } } printf("l=%d t=%d", cmsg->cmsg_level, cmsg->cmsg_type); dump_data_colon: printf(": "); dump_data: printf("{"); for (; n > 0; n--, p++) printf("%02x", *p); print_nl: printf("}\n"); } } void dump_data(void *p, size_t len) { #define INT_PER_LINE (8 * 4) size_t loop; unsigned char buf[41], *b, ch, yoffs; yoffs = 0; b = buf; for (loop = 0; loop < len; loop++) { if (!yoffs) { printf("%08zx: ", loop); yoffs = 1; } ch = *(const char *) p; printf("%02x", ch); *b++ = isprint(ch) ? ch : '.'; p++; if (loop % INT_PER_LINE == INT_PER_LINE - 1) { *b = 0; printf(" %s\n", buf); b = buf; yoffs = 0; } else if (loop % 4 == 3) { putchar(' '); } } if (loop % INT_PER_LINE > 0) { for (loop %= INT_PER_LINE; loop < INT_PER_LINE; loop++) { printf(" "); if (loop % 4 == 3) putchar(' '); } *b = 0; printf("%s\n", buf); } } /*****************************************************************************/ /* * */ int ka_RPC(int service, const void *request, size_t reqlen, void *reply, size_t *_replen) { struct sockaddr_rxrpc srx; struct msghdr msg; struct iovec iov[1]; size_t ctrllen, replen; unsigned char control[4096]; void *preply; int client, ret; client = socket(AF_RXRPC, SOCK_DGRAM, PF_INET); OSERROR(client, "socket"); /* the authentication is associated with the virtual * connection, so we need to open an exclusive channel */ ret = setsockopt(client, SOL_RXRPC, RXRPC_EXCLUSIVE_CONNECTION, NULL, 0); OSERROR(ret, "setsockopt"); /* bind an address to the local endpoint */ srx.srx_family = AF_RXRPC; srx.srx_service = 0; /* it's a client */ srx.transport_type = SOCK_DGRAM; srx.transport_len = sizeof(srx.transport.sin); srx.transport.sin.sin_family = AF_INET; srx.transport.sin.sin_port = htons(7001); memcpy(&srx.transport.sin.sin_addr, &local_addr, 4); ret = bind(client, (struct sockaddr *) &srx, sizeof(srx)); OSERROR(ret, "bind"); /* connect to the remote server */ srx.srx_family = AF_RXRPC; srx.srx_service = service; srx.transport_type = SOCK_DGRAM; srx.transport_len = sizeof(srx.transport.sin); srx.transport.sin.sin_family = AF_INET; srx.transport.sin.sin_port = htons(7004); memcpy(&srx.transport.sin.sin_addr, &remote_addr, 4); ret = connect(client, (struct sockaddr *) &srx, sizeof(srx)); OSERROR(ret, "connect"); /* request an operation */ ctrllen = 0; RXRPC_ADD_CALLID(control, ctrllen, 0x12345); iov[0].iov_base = (void *) request; iov[0].iov_len = reqlen; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = ctrllen; msg.msg_flags = 0; ret = sendmsg(client, &msg, 0); if (ret == -1 && (errno == ENOANO || errno == ECONNABORTED)) perror("sendmsg/data"); else OSERROR(ret, "sendmsg/data"); /* wait for a reply */ preply = reply; replen = 0; while (replen < *_replen) { iov[0].iov_base = preply; iov[0].iov_len = *_replen - replen; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); msg.msg_flags = 0; ret = recvmsg(client, &msg, 0); OSERROR(ret, "recvmsg"); printf("RECV: %d [fl:%d]\n", ret, msg.msg_flags); printf("CMSG: %zu\n", msg.msg_controllen); printf("IOV: %zu [0]=%zu\n", msg.msg_iovlen, iov[0].iov_len); dump_cmsg(&msg); preply += ret; replen += ret; if (msg.msg_flags & MSG_EOR) break; } *_replen = replen; close(client); return 0; } /*****************************************************************************/ /* * authenticate this RxRPC virtual connection with the KA server */ int ka_Authenticate(const char *principal, const char *realm, int service, des_cblock _key, time_t start_time, time_t end_time, struct ka_ticket **_tgt) { struct ka_ticket *tgt; des_key_schedule sched; char buffer[16384] __attribute__((aligned(4))); char data[8], *p, *end, *q; unsigned int tmp; des_cblock key; size_t len; time_t challtime; int ret; memcpy(key, _key, sizeof(key)); ret = des_key_sched(key, sched); OSERROR(ret, "des_key_sched"); tmp = htonl(start_time); memcpy(data, &tmp, 4); memcpy(data + 4, KA_GETTGT_REQ_LABEL, 4); des_pcbc_encrypt((void *) data, (void *) data, sizeof(data), sched, &key, DES_ENCRYPT); /* marshall the arguments */ p = buffer; *(unsigned int *)p = htonl(KAA_AuthenticateV2); p += 4; len = strlen(principal); *(unsigned int *)p = htonl(len); p += 4; memcpy(p, principal, len); p += len; while ((unsigned long) p & 3) *p++ = 0; realm = NULL; if (realm) { len = strlen(realm); *(unsigned int *)p = htonl(len); p += 4; memcpy(p, realm, len); p += len; while ((unsigned long) p & 3) *p++ = 0; } else { *(unsigned int *)p = htonl(0); p += 4; } *(unsigned int *)p = htonl(start_time); p += 4; *(unsigned int *)p = htonl(end_time); p += 4; len = sizeof(data); *(unsigned int *)p = htonl(len); p += 4; memcpy(p, data, len); p += len; while ((unsigned long) p & 3) *p++ = 0; *(unsigned int *)p = htonl(0x3044); p += 4; *(unsigned int *)p = htonl(0); p += 4; /* make the call */ len = sizeof(buffer); ret = ka_RPC(KA_SERVICE, buffer, p - buffer, buffer, &len); OSERROR(buffer, "ka_RPC"); /* unmarshall the reply */ p = buffer; if (len < 8) goto bad_reply; if (ntohl(*(unsigned int *)p) != 0x3044) goto bad_reply; p += 4; len -= 8; tmp = ntohl(*(unsigned int *)p); p += 4; if (((tmp + 3) & ~3) != len) goto bad_reply; if (len < 32 + 5 + 8) goto bad_reply; tgt = malloc(sizeof(struct ka_ticket) + len); if (!tgt) { perror("malloc"); exit(1); } des_pcbc_encrypt((void *) p, (void *) tgt->_reply, len, sched, &key, DES_DECRYPT); dump_data(tgt->_reply, len); end = tgt->_reply + len; /* decode the reply */ memcpy(&tgt->session_key, tgt->_reply + 8, 8); challtime = ntohl(*(unsigned *)(tgt->_reply + 4)); tgt->end_time = ntohl(*(unsigned *)(tgt->_reply + 20)); tgt->kvno = ntohl(*(unsigned *)(tgt->_reply + 24)); tgt->ticket_len = ntohl(*(unsigned *)(tgt->_reply + 28)); p = tgt->_reply + 32; tgt->name = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; tgt->instance = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; tgt->cell = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; tgt->server_name = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; tgt->server_instance = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; tgt->ticket = p; if (tgt->ticket_len > end - p) goto bad_reply; p += tgt->ticket_len; /* validate the reply */ if (memcmp(p, KA_GETTGT_RPL_LABEL, sizeof(KA_GETTGT_RPL_LABEL) - 1) != 0) goto bad_reply; if (challtime != start_time + 1) goto bad_reply; printf("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x\n", tgt->session_key[0], tgt->session_key[1], tgt->session_key[2], tgt->session_key[3], tgt->session_key[4], tgt->session_key[5], tgt->session_key[6], tgt->session_key[7]); printf("KVNO: %u\n", tgt->kvno); printf("TLEN: %u\n", tgt->ticket_len); printf("NAME: %s\n", tgt->name); printf("INST: %s\n", tgt->instance); printf("CELL: %s\n", tgt->cell); printf("SNAM: %s\n", tgt->server_name); printf("SINS: %s\n", tgt->server_instance); *_tgt = tgt; return 0; bad_reply: fprintf(stderr, "Unmarshalling failure\n"); errno = EBADMSG; return -1; } /*****************************************************************************/ /* * */ int ka_GetToken(const char *name, const char *instance, time_t start_time, time_t end_time, const struct ka_ticket *tgt, const char *auth_domain, struct ka_ticket **_ticket) { struct ka_ticket *ticket; des_key_schedule sched; unsigned int tmp; des_cblock key; char buffer[16384] __attribute__((aligned(4))); char tdata[8], data[8], *p, *end, *q; size_t len; int ret; memcpy(key, tgt->session_key, sizeof(key)); dump_data(key, 8); ret = des_key_sched(key, sched); OSERROR(ret, "des_key_sched"); tmp = htonl(start_time); memcpy(tdata, &tmp, 4); tmp = htonl(tgt->end_time); memcpy(tdata + 4, &tmp, 4); dump_data(tdata, 8); des_ecb_encrypt((void *) tdata, (void *) data, sched, DES_ENCRYPT); dump_data(data, 8); memset(tdata, 0xff, 8); ret = des_key_sched(key, sched); des_pcbc_encrypt((void *) data, (void *) tdata, sizeof(data), sched, &key, DES_DECRYPT); dump_data(tdata, 8); /* marshall the arguments */ p = buffer; *(unsigned int *)p = htonl(KAT_GetToken); p += 4; *(unsigned int *)p = htonl(tgt->kvno); p += 4; len = strlen(auth_domain); *(unsigned int *)p = htonl(len); p += 4; memcpy(p, auth_domain, len); p += len; while ((unsigned long) p & 3) *p++ = 0; len = tgt->ticket_len; *(unsigned int *)p = htonl(len); p += 4; memcpy(p, tgt->ticket, len); p += len; while ((unsigned long) p & 3) *p++ = 0; len = strlen("afs"); // tgt->name *(unsigned int *)p = htonl(len); p += 4; memcpy(p, "afs", len); p += len; while ((unsigned long) p & 3) *p++ = 0; len = strlen(tgt->instance); *(unsigned int *)p = htonl(len); p += 4; memcpy(p, tgt->instance, len); p += len; while ((unsigned long) p & 3) *p++ = 0; len = sizeof(data); *(unsigned int *)p = htonl(len); p += 4; memcpy(p, data, len); p += len; *(unsigned int *)p = htonl(0x3044); p += 4; *(unsigned int *)p = htonl(0); p += 4; /* make the call */ len = sizeof(buffer); ret = ka_RPC(KA_TICKET_GRANTING_SERVICE, buffer, p - buffer, buffer, &len); OSERROR(buffer, "ka_RPC"); /* unmarshall the reply */ p = buffer; if (len < 8) goto bad_reply; if (ntohl(*(unsigned int *)p) != 0x3044) goto bad_reply; p += 4; len -= 8; tmp = ntohl(*(unsigned int *)p); p += 4; if (((tmp + 3) & ~3) != len) goto bad_reply; if (len < 32 + 5 + 8) goto bad_reply; ticket = malloc(sizeof(struct ka_ticket) + len); if (!ticket) { perror("malloc"); exit(1); } des_pcbc_encrypt((void *) p, (void *) ticket->_reply, len, sched, &key, DES_DECRYPT); dump_data(ticket->_reply, len); end = ticket->_reply + len; /* decode the reply */ memcpy(&ticket->session_key, ticket->_reply + 8, 8); ticket->end_time = ntohl(*(unsigned *)(ticket->_reply + 20)); ticket->kvno = ntohl(*(unsigned *)(ticket->_reply + 24)); ticket->ticket_len = ntohl(*(unsigned *)(ticket->_reply + 28)); p = ticket->_reply + 32; ticket->name = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; ticket->instance = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; ticket->cell = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; ticket->server_name = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; ticket->server_instance = p; q = memchr(p, 0, end - p); if (!q) goto bad_reply; p = q + 1; ticket->ticket = p; if (ticket->ticket_len > end - p) goto bad_reply; p += ticket->ticket_len; /* validate the reply */ if (memcmp(p, KA_GETTKT_RPL_LABEL, sizeof(KA_GETTKT_RPL_LABEL) - 1) != 0) goto bad_reply; printf("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x\n", ticket->session_key[0], ticket->session_key[1], ticket->session_key[2], ticket->session_key[3], ticket->session_key[4], ticket->session_key[5], ticket->session_key[6], ticket->session_key[7]); printf("KVNO: %u\n", ticket->kvno); printf("TLEN: %u\n", ticket->ticket_len); printf("NAME: %s\n", ticket->name); printf("INST: %s\n", ticket->instance); printf("CELL: %s\n", ticket->cell); printf("SNAM: %s\n", ticket->server_name); printf("SINS: %s\n", ticket->server_instance); *_ticket = ticket; return 0; bad_reply: fprintf(stderr, "Unmarshalling failure\n"); errno = EBADMSG; return -1; } /*****************************************************************************/ /* * */ int ka_GetServerToken(const char *name, const char *instance, const char *cell, unsigned lifetime, struct ka_ticket *tgt, struct ka_ticket **_ticket) { struct timeval tv; int ret; ret = gettimeofday(&tv, NULL); OSERROR(ret, "gettimeofday"); ret = ka_GetToken(name, instance, tv.tv_sec, tv.tv_sec + lifetime, tgt, "CAMBRIDGE.REDHAT.COM", /* auth domain */ _ticket); OSERROR(ret, "ka_GetToken"); return ret; } /*****************************************************************************/ /* * */ int ka_UserAuthenticateGeneral(unsigned long flags, const char *principal, const char *instance, const char *realm, const char *password, unsigned lifetime, long spare1, long spare2, char **reason) { struct rxrpc_key_sec2_v1 *payload; struct timeval tv; des_cblock key; struct ka_ticket *tgt, *ticket; char description[256]; size_t plen; int ret; afs_string_to_key((char *) password, (char *) realm, key); ret = gettimeofday(&tv, NULL); OSERROR(ret, "gettimeofday"); ret = ka_Authenticate(principal, instance, KA_TICKET_GRANTING_SERVICE, key, tv.tv_sec, tv.tv_sec + lifetime, &tgt); OSERROR(ret, "ka_Authenticate"); printf("\n----\n"); ret = ka_GetServerToken(tgt->name, tgt->instance, tgt->cell, 3600 * 60 * 60, tgt, &ticket); OSERROR(ret, "ka_GetServerToken"); plen = sizeof(*payload) + ticket->ticket_len; payload = calloc(1, plen + 4); if (!payload) { perror("calloc"); exit(1); } /* use version 1 of the key data interface */ payload->kver = 1; payload->security_index = 2; payload->ticket_length = ticket->ticket_len; payload->expiry = ticket->end_time; payload->kvno = ticket->kvno; memcpy(payload->session_key, ticket->session_key, 8); memcpy(payload->ticket, ticket->ticket, ticket->ticket_len); sprintf(description, "afs@CAMBRIDGE.REDHAT.COM"); ret = add_key("rxrpc", description, payload, plen, KEY_SPEC_SESSION_KEYRING); OSERROR(ret, "add_key"); return 0; } /*****************************************************************************/ /* * */ int main(int argc, char *argv[]) { char password[100], *reason; int ret; #if 0 des_read_pw_string(password, sizeof(password), "Password: ", 0); printf("---->%s<---\n", password); #endif strcpy(password, "custard2"); ret = ka_UserAuthenticateGeneral(0, "admin", /* principal/username */ "", /* instance */ "cambridge.redhat.com", /* realm */ password, 3600 * 60 * 60, 0, 0, &reason); if (ret < 0) { perror(argv[0]); exit(1); } exit(0); }