xref: /openbsd/usr.sbin/acme-client/fileproc.c (revision 76d0caae)
1 /*	$Id: fileproc.c,v 1.18 2021/07/12 15:09:20 beck 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 <sys/stat.h>
19 
20 #include <err.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "extern.h"
30 
31 static int
32 serialise(const char *real, const char *v, size_t vsz, const char *v2, size_t v2sz)
33 {
34 	int	  fd;
35 	char	 *tmp;
36 
37 	/* create backup hardlink */
38 	if (asprintf(&tmp, "%s.1", real) == -1) {
39 		warn("asprintf");
40 		return 0;
41 	}
42 	(void) unlink(tmp);
43 	if (link(real, tmp) == -1 && errno != ENOENT) {
44 		warn("link");
45 		free(tmp);
46 		return 0;
47 	}
48 	free(tmp);
49 
50 	/*
51 	 * Write into backup location, overwriting.
52 	 * Then atomically do the rename.
53 	 */
54 
55 	if (asprintf(&tmp, "%s.XXXXXXXXXX", real) == -1) {
56 		warn("asprintf");
57 		return 0;
58 	}
59 	if ((fd = mkstemp(tmp)) == -1) {
60 		warn("mkstemp");
61 		goto out;
62 	}
63 	if (fchmod(fd, 0444) == -1) {
64 		warn("fchmod");
65 		goto out;
66 	}
67 	if ((ssize_t)vsz != write(fd, v, vsz)) {
68 		warnx("write");
69 		goto out;
70 	}
71 	if (v2 != NULL && write(fd, v2, v2sz) != (ssize_t)v2sz) {
72 		warnx("write");
73 		goto out;
74 	}
75 	if (close(fd) == -1)
76 		goto out;
77 	if (rename(tmp, real) == -1) {
78 		warn("%s", real);
79 		goto out;
80 	}
81 
82 	free(tmp);
83 	return 1;
84 out:
85 	if (fd != -1)
86 		close(fd);
87 	(void) unlink(tmp);
88 	free(tmp);
89 	return 0;
90 }
91 
92 int
93 fileproc(int certsock, const char *certdir, const char *certfile, const char
94     *chainfile, const char *fullchainfile)
95 {
96 	char		*csr = NULL, *ch = NULL;
97 	size_t		 chsz, csz;
98 	int		 rc = 0;
99 	long		 lval;
100 	enum fileop	 op;
101 
102 	if (unveil(certdir, "rwc") == -1) {
103 		warn("unveil %s", certdir);
104 		goto out;
105 	}
106 
107 	/*
108 	 * rpath and cpath for rename, wpath and cpath for
109 	 * writing to the temporary. fattr for fchmod.
110 	 */
111 	if (pledge("stdio cpath wpath rpath fattr", NULL) == -1) {
112 		warn("pledge");
113 		goto out;
114 	}
115 
116 	/* Read our operation. */
117 
118 	op = FILE__MAX;
119 	if ((lval = readop(certsock, COMM_CHAIN_OP)) == 0)
120 		op = FILE_STOP;
121 	else if (lval == FILE_CREATE || lval == FILE_REMOVE)
122 		op = lval;
123 
124 	if (FILE_STOP == op) {
125 		rc = 1;
126 		goto out;
127 	} else if (FILE__MAX == op) {
128 		warnx("unknown operation from certproc");
129 		goto out;
130 	}
131 
132 	/*
133 	 * If revoking certificates, just unlink the files.
134 	 * We return the special error code of 2 to indicate that the
135 	 * certificates were removed.
136 	 */
137 
138 	if (FILE_REMOVE == op) {
139 		if (certfile) {
140 			if (unlink(certfile) == -1 && errno != ENOENT) {
141 				warn("%s", certfile);
142 				goto out;
143 			} else
144 				dodbg("%s: unlinked", certfile);
145 		}
146 
147 		if (chainfile) {
148 			if (unlink(chainfile) == -1 && errno != ENOENT) {
149 				warn("%s", chainfile);
150 				goto out;
151 			} else
152 				dodbg("%s: unlinked", chainfile);
153 		}
154 
155 		if (fullchainfile) {
156 			if (unlink(fullchainfile) == -1 && errno != ENOENT) {
157 				warn("%s", fullchainfile);
158 				goto out;
159 			} else
160 				dodbg("%s: unlinked", fullchainfile);
161 		}
162 
163 		rc = 2;
164 		goto out;
165 	}
166 
167 	/*
168 	 * Start by downloading the chain PEM as a buffer.
169 	 * This is not NUL-terminated, but we're just going to guess
170 	 * that it's well-formed and not actually touch the data.
171 	 */
172 	if ((ch = readbuf(certsock, COMM_CHAIN, &chsz)) == NULL)
173 		goto out;
174 
175 	if (chainfile) {
176 		if (!serialise(chainfile, ch, chsz, NULL, 0))
177 			goto out;
178 
179 		dodbg("%s: created", 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, csr, csz, NULL, 0))
194 			goto out;
195 
196 		dodbg("%s: created", 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, csr, csz, ch,
207 		    chsz))
208 			goto out;
209 
210 		dodbg("%s: created", fullchainfile);
211 	}
212 
213 	rc = 2;
214 out:
215 	close(certsock);
216 	free(csr);
217 	free(ch);
218 	return rc;
219 }
220