xref: /openbsd-src/usr.sbin/acme-client/fileproc.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /*	$Id: fileproc.c,v 1.14 2017/01/24 13:32:55 jsing Exp $ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <err.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "extern.h"
28 
29 static int
30 serialise(const char *tmp, const char *real,
31     const char *v, size_t vsz, const char *v2, size_t v2sz)
32 {
33 	int	 fd;
34 
35 	/*
36 	 * Write into backup location, overwriting.
37 	 * Then atomically (?) do the rename.
38 	 */
39 
40 	fd = open(tmp, O_WRONLY|O_CREAT|O_TRUNC, 0444);
41 	if (fd == -1) {
42 		warn("%s", tmp);
43 		return 0;
44 	} else if ((ssize_t)vsz != write(fd, v, vsz)) {
45 		warnx("%s", tmp);
46 		close(fd);
47 		return 0;
48 	} else if (v2 != NULL && write(fd, v2, v2sz) != (ssize_t)v2sz) {
49 		warnx("%s", tmp);
50 		close(fd);
51 		return 0;
52 	} else if (close(fd) == -1) {
53 		warn("%s", tmp);
54 		return 0;
55 	} else if (rename(tmp, real) == -1) {
56 		warn("%s", real);
57 		return 0;
58 	}
59 
60 	return 1;
61 }
62 
63 int
64 fileproc(int certsock, const char *certdir, const char *certfile, const char
65     *chainfile, const char *fullchainfile)
66 {
67 	char		*csr = NULL, *ch = NULL;
68 	char		*certfile_bak = NULL, *chainfile_bak = NULL;
69 	char		*fullchainfile_bak = NULL;
70 	size_t		 chsz, csz;
71 	int		 rc = 0;
72 	long		 lval;
73 	enum fileop	 op;
74 
75 	/* File-system and sandbox jailing. */
76 
77 	if (chroot(certdir) == -1) {
78 		warn("chroot");
79 		goto out;
80 	}
81 	if (chdir("/") == -1) {
82 		warn("chdir");
83 		goto out;
84 	}
85 
86 	/*
87 	 * rpath and cpath for rename, wpath and cpath for
88 	 * writing to the temporary.
89 	 */
90 	if (pledge("stdio cpath wpath rpath", NULL) == -1) {
91 		warn("pledge");
92 		goto out;
93 	}
94 
95 	/* Read our operation. */
96 
97 	op = FILE__MAX;
98 	if ((lval = readop(certsock, COMM_CHAIN_OP)) == 0)
99 		op = FILE_STOP;
100 	else if (lval == FILE_CREATE || lval == FILE_REMOVE)
101 		op = lval;
102 
103 	if (FILE_STOP == op) {
104 		rc = 1;
105 		goto out;
106 	} else if (FILE__MAX == op) {
107 		warnx("unknown operation from certproc");
108 		goto out;
109 	}
110 
111 	/*
112 	 * If revoking certificates, just unlink the files.
113 	 * We return the special error code of 2 to indicate that the
114 	 * certificates were removed.
115 	 */
116 
117 	if (FILE_REMOVE == op) {
118 		if (certfile) {
119 			if (unlink(certfile) == -1 && errno != ENOENT) {
120 				warn("%s/%s", certdir, certfile);
121 				goto out;
122 			} else
123 				dodbg("%s/%s: unlinked", certdir, certfile);
124 		}
125 
126 		if (chainfile) {
127 			if (unlink(chainfile) == -1 && errno != ENOENT) {
128 				warn("%s/%s", certdir, chainfile);
129 				goto out;
130 			} else
131 				dodbg("%s/%s: unlinked", certdir, chainfile);
132 		}
133 
134 		if (fullchainfile) {
135 			if (unlink(fullchainfile) == -1 && errno != ENOENT) {
136 				warn("%s/%s", certdir, fullchainfile);
137 				goto out;
138 			} else
139 				dodbg("%s/%s: unlinked", certdir,
140 				    fullchainfile);
141 		}
142 
143 		rc = 2;
144 		goto out;
145 	}
146 
147 	/*
148 	 * Start by downloading the chain PEM as a buffer.
149 	 * This is not NUL-terminated, but we're just going to guess
150 	 * that it's well-formed and not actually touch the data.
151 	 * Once downloaded, dump it into CHAIN_BAK.
152 	 */
153 
154 	if (certfile)
155 		if (asprintf(&certfile_bak, "%s~", certfile) == -1) {
156 			warn("asprintf");
157 			goto out;
158 		}
159 
160 	if (chainfile)
161 		if (asprintf(&chainfile_bak, "%s~", chainfile) == -1) {
162 			warn("asprintf");
163 			goto out;
164 		}
165 
166 	if (fullchainfile)
167 		if (asprintf(&fullchainfile_bak, "%s~", fullchainfile) == -1) {
168 			warn("asprintf");
169 			goto out;
170 		}
171 
172 	if ((ch = readbuf(certsock, COMM_CHAIN, &chsz)) == NULL)
173 		goto out;
174 
175 	if (chainfile) {
176 		if (!serialise(chainfile_bak, chainfile, ch, chsz, NULL, 0))
177 			goto out;
178 
179 		dodbg("%s/%s: created", certdir, chainfile);
180 	}
181 
182 	/*
183 	 * Next, wait until we receive the DER encoded (signed)
184 	 * certificate from the network process.
185 	 * This comes as a stream of bytes: we don't know how many, so
186 	 * just keep downloading.
187 	 */
188 
189 	if ((csr = readbuf(certsock, COMM_CSR, &csz)) == NULL)
190 		goto out;
191 
192 	if (certfile) {
193 		if (!serialise(certfile_bak, certfile, csr, csz, NULL, 0))
194 			goto out;
195 
196 		dodbg("%s/%s: created", certdir, certfile);
197 	}
198 
199 	/*
200 	 * Finally, create the full-chain file.
201 	 * This is just the concatenation of the certificate and chain.
202 	 * We return the special error code 2 to indicate that the
203 	 * on-file certificates were changed.
204 	 */
205 	if (fullchainfile) {
206 		if (!serialise(fullchainfile_bak, fullchainfile, csr, csz, ch,
207 		    chsz))
208 			goto out;
209 
210 		dodbg("%s/%s: created", certdir, fullchainfile);
211 	}
212 
213 	rc = 2;
214 out:
215 	close(certsock);
216 	free(csr);
217 	free(ch);
218 	free(certfile_bak);
219 	free(chainfile_bak);
220 	free(fullchainfile_bak);
221 	return rc;
222 }
223