1/*
2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "kf_locl.h"
35RCSID("$Id$");
36
37krb5_context context;
38char krb5_tkfile[MAXPATHLEN];
39
40static int help_flag;
41static int version_flag;
42static char *port_str;
43char *service = KF_SERVICE;
44int do_inetd = 0;
45static char *regpag_str=NULL;
46
47static struct getargs args[] = {
48    { "port", 'p', arg_string, &port_str, "port to listen to", "port" },
49    { "inetd",'i',arg_flag, &do_inetd,
50       "Not started from inetd", NULL },
51    { "regpag",'R',arg_string,&regpag_str,"path to regpag binary","regpag"},
52    { "help", 'h', arg_flag, &help_flag },
53    { "version", 0, arg_flag, &version_flag }
54};
55
56static int num_args = sizeof(args) / sizeof(args[0]);
57
58static void
59usage(int code, struct getargs *args, int num_args)
60{
61    arg_printusage(args, num_args, NULL, "");
62    exit(code);
63}
64
65static int
66server_setup(krb5_context *context, int argc, char **argv)
67{
68    int port = 0;
69    int local_argc;
70
71    local_argc = krb5_program_setup(context, argc, argv, args, num_args, usage);
72
73    if(help_flag)
74	(*usage)(0, args, num_args);
75    if(version_flag) {
76	print_version(NULL);
77	exit(0);
78    }
79
80    if(port_str){
81	struct servent *s = roken_getservbyname(port_str, "tcp");
82	if(s)
83	    port = s->s_port;
84	else {
85	    char *ptr;
86
87	    port = strtol (port_str, &ptr, 10);
88	    if (port == 0 && ptr == port_str)
89		errx (1, "Bad port `%s'", port_str);
90	    port = htons(port);
91	}
92    }
93
94    if (port == 0)
95	port = krb5_getportbyname (*context, KF_PORT_NAME, "tcp", KF_PORT_NUM);
96
97    if(argv[local_argc] != NULL)
98        usage(1, args, num_args);
99
100    return port;
101}
102
103static int protocol_version;
104
105static krb5_boolean
106kfd_match_version(const void *arg, const char *version)
107{
108    if(strcmp(version, KF_VERSION_1) == 0) {
109	protocol_version = 1;
110	return TRUE;
111    } else if (strlen(version) == 4 &&
112	       version[0] == '0' &&
113	       version[1] == '.' &&
114	       (version[2] == '4' || version[2] == '3') &&
115	       islower((unsigned char)version[3])) {
116	protocol_version = 0;
117	return TRUE;
118    }
119    return FALSE;
120}
121
122static int
123proto (int sock, const char *service)
124{
125    krb5_auth_context auth_context;
126    krb5_error_code status;
127    krb5_principal server;
128    krb5_ticket *ticket;
129    char *name;
130    char ret_string[10];
131    char hostname[MAXHOSTNAMELEN];
132    krb5_data data;
133    krb5_data remotename;
134    krb5_data tk_file;
135    krb5_ccache ccache;
136    char ccname[MAXPATHLEN];
137    struct passwd *pwd;
138
139    status = krb5_auth_con_init (context, &auth_context);
140    if (status)
141	krb5_err(context, 1, status, "krb5_auth_con_init");
142
143    status = krb5_auth_con_setaddrs_from_fd (context,
144					     auth_context,
145					     &sock);
146    if (status)
147	krb5_err(context, 1, status, "krb5_auth_con_setaddr");
148
149    if(gethostname (hostname, sizeof(hostname)) < 0)
150	krb5_err(context, 1, errno, "gethostname");
151
152    status = krb5_sname_to_principal (context,
153				      hostname,
154				      service,
155				      KRB5_NT_SRV_HST,
156				      &server);
157    if (status)
158	krb5_err(context, 1, status, "krb5_sname_to_principal");
159
160    status = krb5_recvauth_match_version (context,
161					  &auth_context,
162					  &sock,
163					  kfd_match_version,
164					  NULL,
165					  server,
166					  0,
167					  NULL,
168					  &ticket);
169    if (status)
170	krb5_err(context, 1, status, "krb5_recvauth");
171
172    status = krb5_unparse_name (context,
173				ticket->client,
174				&name);
175    if (status)
176	krb5_err(context, 1, status, "krb5_unparse_name");
177
178    if(protocol_version == 0) {
179	data.data = "old clnt"; /* XXX old clients only had room for
180                                   10 bytes of message, and also
181                                   didn't show it to the user */
182	data.length = strlen(data.data) + 1;
183	krb5_write_message(context, &sock, &data);
184	sleep(2); /* XXX give client time to finish */
185	krb5_errx(context, 1, "old client; exiting");
186    }
187
188    status=krb5_read_priv_message (context, auth_context,
189				   &sock, &remotename);
190    if (status)
191	krb5_err(context, 1, status, "krb5_read_message");
192    status=krb5_read_priv_message (context, auth_context,
193				   &sock, &tk_file);
194    if (status)
195	krb5_err(context, 1, status, "krb5_read_message");
196
197    krb5_data_zero (&data);
198
199    if(((char*)remotename.data)[remotename.length-1] != '\0')
200	krb5_errx(context, 1, "unterminated received");
201    if(((char*)tk_file.data)[tk_file.length-1] != '\0')
202	krb5_errx(context, 1, "unterminated received");
203
204    status = krb5_read_priv_message(context, auth_context, &sock, &data);
205
206    if (status) {
207	krb5_err(context, 1, errno, "krb5_read_priv_message");
208	goto out;
209    }
210
211    pwd = getpwnam ((char *)(remotename.data));
212    if (pwd == NULL) {
213	status=1;
214	krb5_warnx(context, "getpwnam: %s failed",(char *)(remotename.data));
215	goto out;
216    }
217
218    if(!krb5_kuserok (context,
219		      ticket->client,
220		      (char *)(remotename.data))) {
221	status=1;
222	krb5_warnx(context, "krb5_kuserok: permission denied");
223	goto out;
224    }
225
226    if (setgid(pwd->pw_gid) < 0) {
227	krb5_warn(context, errno, "setgid");
228	goto out;
229    }
230    if (setuid(pwd->pw_uid) < 0) {
231	krb5_warn(context, errno, "setuid");
232	goto out;
233    }
234
235    if (tk_file.length != 1)
236	snprintf (ccname, sizeof(ccname), "%s", (char *)(tk_file.data));
237    else
238	snprintf (ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%lu",
239		  (unsigned long)pwd->pw_uid);
240
241    status = krb5_cc_resolve (context, ccname, &ccache);
242    if (status) {
243	krb5_warn(context, status, "krb5_cc_resolve");
244        goto out;
245    }
246    status = krb5_cc_initialize (context, ccache, ticket->client);
247    if (status) {
248	krb5_warn(context, status, "krb5_cc_initialize");
249        goto out;
250    }
251    status = krb5_rd_cred2 (context, auth_context, ccache, &data);
252    krb5_cc_close (context, ccache);
253    if (status) {
254	krb5_warn(context, status, "krb5_rd_cred");
255        goto out;
256
257    }
258    strlcpy(krb5_tkfile,ccname,sizeof(krb5_tkfile));
259    krb5_warnx(context, "%s forwarded ticket to %s,%s",
260	       name,
261	       (char *)(remotename.data),ccname);
262  out:
263    if (status) {
264	strlcpy(ret_string, "no", sizeof(ret_string));
265	krb5_warnx(context, "failed");
266    } else  {
267	strlcpy(ret_string, "ok", sizeof(ret_string));
268    }
269
270    krb5_data_free (&tk_file);
271    krb5_data_free (&remotename);
272    krb5_data_free (&data);
273    free(name);
274
275    data.data = ret_string;
276    data.length = strlen(ret_string) + 1;
277    status = krb5_write_priv_message(context, auth_context, &sock, &data);
278    krb5_auth_con_free(context, auth_context);
279
280    return status;
281}
282
283static int
284doit (int port, const char *service)
285{
286    if (do_inetd)
287	mini_inetd(port, NULL);
288    return proto (STDIN_FILENO, service);
289}
290
291int
292main(int argc, char **argv)
293{
294    int port;
295    int ret;
296    krb5_log_facility *fac;
297
298    setprogname (argv[0]);
299    roken_openlog (argv[0], LOG_ODELAY | LOG_PID,LOG_AUTH);
300    port = server_setup(&context, argc, argv);
301    ret = krb5_openlog(context, "kfd", &fac);
302    if(ret) krb5_err(context, 1, ret, "krb5_openlog");
303    ret = krb5_set_warn_dest(context, fac);
304    if(ret) krb5_err(context, 1, ret, "krb5_set_warn_dest");
305
306    ret = doit (port, service);
307    closelog();
308    if (ret == 0 && regpag_str != NULL)
309        ret = execl(regpag_str, "regpag", "-t", krb5_tkfile, "-r", NULL);
310    return ret;
311}
312