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