1 /* cvm/client.c - CVM client library
2 * Copyright (C) 2010 Bruce Guenter <bruce@untroubled.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 #include <sys/types.h>
19 #include <netdb.h>
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25
26 #include <bglibs/sysdeps.h>
27 #include <bglibs/socket.h>
28 #include <bglibs/striter.h>
29 #include <bglibs/str.h>
30
31 #include "v2client.h"
32 #include "credentials.h"
33 #include "protocol.h"
34 #include "random.h"
35
36 const char* cvm_client_account_split_chars = "@";
37
38 static struct cvm_packet request;
39 static struct cvm_packet response;
40
41 static struct
42 {
43 unsigned type;
44 unsigned start;
45 } offsets[CVM_BUFSIZE/2];
46 static str randombytes;
47
48 /* Packet management code ****************************************************/
parse_packet(struct cvm_packet * p)49 static int parse_packet(struct cvm_packet* p)
50 {
51 unsigned i;
52 unsigned o;
53 if (p->length < 3)
54 return CVME_BAD_MODDATA;
55 if (p->data[1] != randombytes.len)
56 return CVME_BAD_MODDATA;
57 if (memcmp(p->data+2, randombytes.s, randombytes.len) != 0)
58 return CVME_BAD_MODDATA;
59 if (p->data[p->length-1] != 0)
60 return CVME_BAD_MODDATA;
61 /* This funny loop gives all the strings in the p->data NUL termination. */
62 for (i = 0, o = p->data[1] + 2;
63 o < sizeof p->data && p->data[o] != 0;
64 ++i, o += p->data[o+1] + 2) {
65 offsets[i].type = p->data[o];
66 offsets[i].start = o+2;
67 p->data[o] = 0;
68 }
69 offsets[i].type = offsets[i].start = 0;
70 if (p->data[0] != 0)
71 return p->data[0];
72 /* Extract required and common facts. */
73 if (cvm_client_fact_str(CVM_FACT_USERNAME, &cvm_fact_username, &i) ||
74 cvm_client_fact_uint(CVM_FACT_USERID, &cvm_fact_userid) ||
75 cvm_client_fact_uint(CVM_FACT_GROUPID, &cvm_fact_groupid) ||
76 cvm_client_fact_str(CVM_FACT_DIRECTORY, &cvm_fact_directory, &i))
77 return CVME_BAD_MODDATA;
78 cvm_client_fact_str(CVM_FACT_SHELL, &cvm_fact_shell, &i);
79 cvm_client_fact_str(CVM_FACT_REALNAME, &cvm_fact_realname, &i);
80 cvm_client_fact_str(CVM_FACT_GROUPNAME, &cvm_fact_groupname, &i);
81 cvm_client_fact_str(CVM_FACT_SYS_USERNAME, &cvm_fact_sys_username, &i);
82 cvm_client_fact_str(CVM_FACT_SYS_DIRECTORY, &cvm_fact_sys_directory, &i);
83 cvm_client_fact_str(CVM_FACT_DOMAIN, &cvm_fact_domain, &i);
84 cvm_client_fact_str(CVM_FACT_MAILBOX, &cvm_fact_mailbox, &i);
85 return 0;
86 }
87
packet_add(struct cvm_packet * p,unsigned type,unsigned len,const char * data)88 static unsigned packet_add(struct cvm_packet* p, unsigned type,
89 unsigned len, const char* data)
90 {
91 unsigned char* ptr;
92 if (p->length + len + 2 >= CVM_BUFSIZE-1)
93 return 0;
94 ptr = p->data + p->length;
95 *ptr++ = type;
96 *ptr++ = len;
97 memcpy(ptr, data, len);
98 p->length += len + 2;
99 return 1;
100 }
101
make_randombytes(void)102 static void make_randombytes(void)
103 {
104 static int initialized = 0;
105 unsigned i;
106 const char *e;
107
108 if (!initialized) {
109 cvm_random_init();
110
111 if (randombytes.len == 0) {
112 if ((e = getenv("CVM_RANDOM_BYTES")) != 0)
113 i = atoi(e);
114 else
115 i = 8;
116 str_ready(&randombytes, i);
117 randombytes.len = i;
118 }
119 initialized = 1;
120 }
121
122 cvm_random_fill((unsigned char*)randombytes.s, randombytes.len);
123 }
124
build_packet(struct cvm_packet * p,unsigned count,const struct cvm_credential * credentials,int addrandom)125 static unsigned build_packet(struct cvm_packet* p,
126 unsigned count,
127 const struct cvm_credential* credentials,
128 int addrandom)
129 {
130 const char* env;
131 unsigned i;
132 int has_secret;
133
134 if (addrandom)
135 make_randombytes();
136 else
137 randombytes.len = 0;
138 p->length = 0;
139 if (!packet_add(p, CVM2_PROTOCOL, randombytes.len, randombytes.s))
140 return 0;
141
142 for (i = 0, has_secret = 0; i < count; ++i, ++credentials) {
143 if (credentials->type == CVM_CRED_SECRET)
144 has_secret = 1;
145 if (!packet_add(p, credentials->type,
146 credentials->value.len, credentials->value.s))
147 return 0;
148 }
149
150 if (!has_secret
151 && (env = getenv("CVM_LOOKUP_SECRET")) != 0)
152 if (!packet_add(p, CVM_CRED_SECRET, strlen(env), env))
153 return 0;
154
155 p->data[p->length++] = 0;
156 return 1;
157 }
158
cvm_client_fact_str(unsigned number,const char ** data,unsigned * length)159 int cvm_client_fact_str(unsigned number, const char** data, unsigned* length)
160 {
161 static unsigned last_offset = 0;
162 static unsigned last_number = -1;
163 unsigned o;
164 int err = CVME_NOFACT;
165
166 o = (number != last_number || offsets[last_offset].type == 0)
167 ? 0
168 : last_offset;
169 last_number = number;
170
171 while (offsets[o].type != 0) {
172 if (offsets[o++].type == number) {
173 *length = (*data = (char*)response.data + offsets[o-1].start)[-1];
174 err = 0;
175 break;
176 }
177 }
178 last_offset = o;
179 return err;
180 }
181
cvm_client_fact_uint(unsigned number,unsigned long * data)182 int cvm_client_fact_uint(unsigned number, unsigned long* data)
183 {
184 const char* ptr;
185 unsigned len;
186 unsigned long i;
187 int err;
188
189 if ((err = cvm_client_fact_str(number, &ptr, &len)) != 0) return err;
190
191 for (i = 0; len > 0 && *ptr >= '0' && *ptr <= '9'; ++ptr, --len) {
192 unsigned long tmp = i;
193 i = (i * 10) + (*ptr - '0');
194 if (i < tmp)
195 return CVME_BAD_MODDATA;
196 }
197 if (len > 0)
198 return CVME_BAD_MODDATA;
199 *data = i;
200 return 0;
201 }
202
cvm_client_split_account(str * account,str * domain)203 int cvm_client_split_account(str* account, str* domain)
204 {
205 unsigned actlen;
206 char* actptr;
207 unsigned i;
208 const char* sc;
209 actlen = account->len;
210 actptr = account->s;
211 if ((sc = getenv("CVM_ACCOUNT_SPLIT_CHARS")) == 0)
212 sc = cvm_client_account_split_chars;
213 i = actlen;
214 while (i-- > 0) {
215 if (strchr(sc, actptr[i]) != 0) {
216 if (!str_copyb(domain, actptr + i + 1, actlen - i - 1))
217 return 0;
218 account->s[account->len = i] = 0;
219 break;
220 }
221 }
222 return 1;
223 }
224
225 /* Top-level wrapper *********************************************************/
cvm_client_authenticate(const char * modules,unsigned count,const struct cvm_credential * credentials)226 int cvm_client_authenticate(const char* modules, unsigned count,
227 const struct cvm_credential* credentials)
228 {
229 int result;
230 void (*oldsig)(int);
231 int addrandom;
232 static str module_list;
233 striter i;
234 unsigned long u;
235
236 /* Make a copy of the module list so we can make the strings NUL
237 * terminated internally. */
238 if (!str_copys(&module_list, modules))
239 return CVME_IO | CVME_FATAL;
240 str_subst(&module_list, ',', '\0');
241
242 /* Set addrandom to true if any module uses UDP. */
243 addrandom = 0;
244 striter_loop(&i, &module_list, '\0') {
245 if (memcmp(i.startptr, "cvm-udp:", 8) == 0) {
246 addrandom = 1;
247 break;
248 }
249 }
250
251 if (!build_packet(&request, count, credentials, addrandom))
252 return CVME_GENERAL;
253
254 oldsig = signal(SIGPIPE, SIG_IGN);
255
256 /* Invoke each module in the list, exiting when any module produces a
257 * non-PERMFAIL result, or when it produces a PERMFAIL result with
258 * OUTOFSCOPE set to 0. */
259 striter_loop(&i, &module_list, '\0') {
260 if (!memcmp(i.startptr, "cvm-udp:", 8))
261 result = cvm_xfer_udp_packets(i.startptr+8, &request, &response);
262 else if (!memcmp(i.startptr, "cvm-local:", 10))
263 result = cvm_xfer_local_packets(i.startptr+10, &request, &response);
264 else {
265 if (!memcmp(i.startptr, "cvm-command:", 12))
266 i.startptr += 12;
267 result = cvm_xfer_command_packets(i.startptr, &request, &response);
268 }
269 /* Note: the result returned by cvm_xfer_* indicates if transmission
270 * succeeded, not the actual result of validation. The validation
271 * result is returned by parse_packet. */
272 if (result == 0)
273 result = parse_packet(&response);
274 /* Return success and temporary failures. */
275 if (result != CVME_PERMFAIL)
276 break;
277 /* Also return permanent failure if the result is in scope. */
278 if (cvm_client_fact_uint(CVM_FACT_OUTOFSCOPE, &u) == 0
279 && u == 0)
280 break;
281 }
282 signal(SIGPIPE, oldsig);
283 return result;
284 }
285