Added initial unfs3 sources for version 0.9.22+dfsg-1maemo2
[unfs3] / unfs3 / Extras / cluster.c
1
2 /*
3  * UNFS3 cluster support
4  * (C) 2004, Pascal Schmidt
5  * see file LICENSE for license details
6  */
7
8 #include "../config.h"
9
10 #ifdef WANT_CLUSTER
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <sys/socket.h>
15 #include <rpc/rpc.h>
16 #include <dirent.h>
17 #include <errno.h>
18 #include <libgen.h>
19 #include <netdb.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <syslog.h>
23 #include <unistd.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26
27 #include "../nfs.h"
28 #include "../daemon.h"
29 #include "../backend.h"
30 #include "cluster.h"
31
32 /* array of dirents prefixed with master file name */
33 static char **cluster_dirents = NULL;
34
35 /* number of dirents in above array */
36 static int cluster_count = -1;
37
38 /*
39  * check whether given pathname is in clustering path
40  */
41 int want_cluster(const char *path)
42 {
43     char buf[NFS_MAXPATHLEN];
44     char *last, *next;
45
46     /* if path is too long, play it safe */
47     if (strlen(opt_cluster_path) + 1 > NFS_MAXPATHLEN)
48         return TRUE;
49
50     strcpy(buf, opt_cluster_path);
51     last = buf;
52
53     /* iterate over colon-seperated list */
54     do {
55         next = strchr(last, ':');
56         if (next)
57             *next = 0;
58
59         if (strstr(path, last) == path)
60             return TRUE;
61
62         if (next) {
63             last = next + 1;
64             if (strlen(last) == 0)
65                 last = NULL;
66         } else {
67             last = NULL;
68         }
69     } while (last);
70
71     return FALSE;
72 }
73
74 /*
75  * get name of remote machine
76  */
77 static char *get_host(struct in_addr remote)
78 {
79     static char buf[NFS_MAXPATHLEN];
80     struct hostent *entry;
81     char *dot;
82
83     entry = gethostbyaddr((char *) &remote, sizeof(struct in_addr), AF_INET);
84
85     if (entry) {
86         strcpy(buf, entry->h_name);
87
88         /* have the name string end at the first dot */
89         dot = strchr(buf, '.');
90         if (dot)
91             *dot = 0;
92
93         return buf;
94     }
95
96     return NULL;
97 }
98
99 /*
100  * check whether name is already host tagged name
101  */
102 int is_host(const char *name)
103 {
104     return (int) (strstr(name, "$$HOST=") && name[strlen(name) - 1] == '$' &&
105                   name[strlen(name) - 2] == '$');
106 }
107
108 /*
109  * check whether a hostname matches a dirent
110  */
111 char *match_host(const char *hname, const char *entry)
112 {
113     char buf[NFS_MAXPATHLEN];
114     static char *part;
115
116     /* check for presence of hostname tag */
117     if (!is_host(entry))
118         return NULL;
119
120     part = strstr(entry, "$$HOST=");
121
122     /* copy hostname part of host tag */
123     memcpy(buf, part + 7, strlen(part) - 8);
124     buf[strlen(part) - 9] = 0;
125
126     /* exact match? */
127     if (strcmp(buf, hname) == 0)
128         return part;
129
130     /* wildcard pattern? */
131     if (buf[strlen(buf) - 1] != '*')
132         return NULL;
133
134     /* if wildcard, check for matching prefix */
135     buf[strlen(buf) - 1] = 0;
136     if (strstr(hname, buf) == hname)
137         return part;
138
139     return NULL;
140 }
141
142 /*
143  * better dirname providing internal buffer
144  */
145 char *cluster_dirname(const char *path)
146 {
147     static char buf[NFS_MAXPATHLEN];
148
149     strcpy(buf, path);
150     return dirname(buf);
151 }
152
153 /*
154  * better basename providing internal buffer
155  */
156 char *cluster_basename(const char *path)
157 {
158     static char buf[NFS_MAXPATHLEN];
159
160     strcpy(buf, path);
161     return basename(buf);
162 }
163
164 /*
165  * free dirent array
166  */
167 void cluster_freedir(void)
168 {
169     /* only if it was really allocated before */
170     if (cluster_dirents) {
171         while (cluster_count--)
172             free(cluster_dirents[cluster_count]);
173         free(cluster_dirents);
174         cluster_dirents = NULL;
175     }
176 }
177
178 /*
179  * compare function for qsort'ing the scandir list
180  */
181 int compar(const void *x, const void *y)
182 {
183     return strcmp(*(const char **) x, *(const char **) y);
184 }
185
186 /*
187  * reset euid/egid to specific values
188  */
189 static void reset_ids(uid_t euid, gid_t egid)
190 {
191     if (backend_setegid(egid) || backend_seteuid(euid)) {
192         logmsg(LOG_EMERG, "euid/egid switching failed, aborting");
193         daemon_exit(CRISIS);
194     }
195 }
196
197 /*
198  * scan directory for filenames beginning with master name as prefix
199  */
200 void cluster_scandir(const char *path)
201 {
202     char prefix[NFS_MAXPATHLEN];
203     DIR *scan;
204     struct dirent *entry;
205     char **new, *name;
206     uid_t euid;
207     gid_t egid;
208
209     strcpy(prefix, cluster_basename(path));
210
211     /* 
212      * need to read directory as root, temporarily switch back
213      */
214     euid = backend_geteuid();
215     egid = backend_getegid();
216     backend_setegid(0);
217     backend_seteuid(0);
218
219     scan = backend_opendir(cluster_dirname(path));
220     if (!scan) {
221         cluster_count = -1;
222         reset_ids(euid, egid);
223         return;
224     }
225
226     cluster_count = 0;
227     while ((entry = backend_readdir(scan))) {
228         if (strstr(entry->d_name, prefix) != entry->d_name &&
229             strcmp(entry->d_name, "$$CREATE=IP$$") != 0 &&
230             strcmp(entry->d_name, "$$CREATE=CLIENT$$") != 0 &&
231             strcmp(entry->d_name, "$$ALWAYS=IP$$") != 0 &&
232             strcmp(entry->d_name, "$$ALWAYS=CLIENT$$") != 0)
233             continue;
234
235         name = malloc(strlen(entry->d_name) + 1);
236         new = realloc(cluster_dirents, (cluster_count + 1) * sizeof(char *));
237         if (!new || !name) {
238             cluster_freedir();
239             cluster_count = -1;
240             free(new);
241             free(name);
242             backend_closedir(scan);
243             reset_ids(euid, egid);
244             return;
245         }
246
247         strcpy(name, entry->d_name);
248         cluster_dirents = new;
249         cluster_dirents[cluster_count] = name;
250         cluster_count++;
251     }
252
253     backend_closedir(scan);
254     reset_ids(euid, egid);
255
256     /* list needs to be sorted for cluster_lookup_lowlevel to work */
257     qsort(cluster_dirents, cluster_count, sizeof(char *), compar);
258 }
259
260 /*
261  * check whether master name + suffix matches with a string
262  */
263 int match_suffix(const char *master, const char *suffix, const char *entry)
264 {
265     char obj[NFS_MAXPATHLEN];
266
267     sprintf(obj, "%s%s", master, suffix);
268
269     if (strcmp(entry, obj) == 0)
270         return CLU_SLAVE;
271     else
272         return FALSE;
273 }
274
275 /*
276  * create string version of a netmask
277  * buf:    where to put string
278  * remote: full IP address of remote machine
279  * n:      number of dots to keep
280  */
281 void cluster_netmask(char *buf, const char *remote, int n)
282 {
283     int i;
284
285     sprintf(buf, "$$IP=%s", remote);
286
287     /* skip to desired dot position */
288     for (i = 0; i < n; i++)
289         buf = strchr(buf, '.') + 1;
290
291     *buf-- = 0;
292
293     /* append trailer of netmask string */
294     switch (n) {
295         case 3:
296             strcat(buf, "0_24$$");
297             break;
298         case 2:
299             strcat(buf, "0.0_16$$");
300             break;
301         case 1:
302             strcat(buf, "0.0.0_8$$");
303             break;
304     }
305 }
306
307 /*
308  * look up cluster name, defaulting to master name if no slave name found
309  */
310 int cluster_lookup_lowlevel(char *path, struct svc_req *rqstp)
311 {
312     struct in_addr raddr;
313     char *remote, *hname, *master, *entry, *match;
314     char buf[NFS_MAXPATHLEN];
315     int i, res = CLU_MASTER;
316
317     cluster_freedir();
318     cluster_scandir(path);
319
320     if (cluster_count == -1)
321         return CLU_IO;
322     else if (cluster_count == 0)
323         return CLU_MASTER;
324
325     raddr = get_remote(rqstp);         /* remote IP address */
326     master = cluster_basename(path);   /* master file name */
327     remote = inet_ntoa(raddr);         /* remote IP address string */
328     hname = get_host(raddr);           /* remote hostname */
329
330     /* 
331      * traversal in reverse alphanumerical order, so that 
332      *  IP is encountered before HOST, HOST before CLIENT,
333      *  CLIENT before ALWAYS, and also subnets are encountered
334      *  in the right order
335      */
336     i = cluster_count;
337     while (i--) {
338         entry = cluster_dirents[i];
339
340         /* match specific IP address */
341         sprintf(buf, "$$IP=%s$$", remote);
342         if ((res = match_suffix(master, buf, entry)))
343             break;
344
345         /* always match IP file */
346         if ((res = match_suffix(master, "$$ALWAYS=IP$$", entry)))
347             break;
348         if (strcmp("$$ALWAYS=IP$$", entry) == 0) {
349             res = CLU_SLAVE;
350             break;
351         }
352
353         /* match all clients */
354         strcpy(buf, "$$CLIENT$$");
355         if ((res = match_suffix(master, buf, entry)))
356             break;
357
358         /* always match CLIENT file */
359         if ((res = match_suffix(master, "$$ALWAYS=CLIENT$$", entry)))
360             break;
361         if (strcmp("$$ALWAYS=CLIENT$$", entry) == 0) {
362             res = CLU_SLAVE;
363             break;
364         }
365
366         /* match 24 bit network address */
367         cluster_netmask(buf, remote, 3);
368         if ((res = match_suffix(master, buf, entry)))
369             break;
370
371         /* match 16 bit network address */
372         cluster_netmask(buf, remote, 2);
373         if ((res = match_suffix(master, buf, entry)))
374             break;
375
376         /* match 8 bit network address */
377         cluster_netmask(buf, remote, 1);
378         if ((res = match_suffix(master, buf, entry)))
379             break;
380
381         /* match hostname pattern */
382         if (!is_host(master)) {
383             match = match_host(hname, entry);
384             if (match) {
385                 res = CLU_SLAVE;
386                 strcpy(buf, match);
387                 break;
388             }
389         }
390     }
391
392     /* append suffix if possible */
393     if (res == CLU_SLAVE) {
394         if (strlen(path) + strlen(buf) + 1 < NFS_MAXPATHLEN)
395             strcat(path, buf);
396         else
397             res = CLU_TOOLONG;
398     } else {
399         /* res will be 0 after above loop */
400         res = CLU_MASTER;
401     }
402
403     /* 
404      * dirent array not freed here since cluster_create may need
405      * to look at it afterwards
406      */
407
408     return res;
409 }
410
411 /*
412  * substitute slave filename if possible
413  */
414 void cluster_lookup(char *path, struct svc_req *rqstp, nfsstat3 * nstat)
415 {
416     int res;
417
418     if (!opt_cluster)
419         return;
420
421     if (!path)
422         return;
423
424     if (*nstat != NFS3_OK)
425         return;
426
427     if (!want_cluster(path))
428         return;
429
430     res = strlen(path);
431     if (strstr(path, "$$$$") == path + res - 4) {
432         *(path + res - 4) = 0;
433         return;
434     }
435
436     res = cluster_lookup_lowlevel(path, rqstp);
437     if (res == CLU_TOOLONG)
438         *nstat = NFS3ERR_NAMETOOLONG;
439     else if (res == CLU_IO)
440         *nstat = NFS3ERR_IO;
441 }
442
443 /*
444  * substitute slave filename if possible, for create operations
445  */
446 void cluster_create(char *path, struct svc_req *rqstp, nfsstat3 * nstat)
447 {
448     int i, res;
449     char buf[NFS_MAXPATHLEN];
450     char *master, *entry;
451
452     if (!opt_cluster)
453         return;
454
455     if (*nstat != NFS3_OK)
456         return;
457
458     if (!want_cluster(path))
459         return;
460
461     res = cluster_lookup_lowlevel(path, rqstp);
462
463     if (res == CLU_TOOLONG) {
464         *nstat = NFS3ERR_NAMETOOLONG;
465         return;
466     } else if (res == CLU_IO) {
467         *nstat = NFS3ERR_IO;
468         return;
469     } else if (res == CLU_SLAVE)
470         return;
471
472     master = cluster_basename(path);
473
474     /* look for create tag */
475     i = cluster_count;
476     while (i--) {
477         entry = cluster_dirents[i];
478
479         /* always create IP file */
480         sprintf(buf, "$$IP=%s$$", inet_ntoa(get_remote(rqstp)));
481         if ((res = match_suffix(master, "$$CREATE=IP$$", entry)) ||
482             (res = match_suffix(master, "$$ALWAYS=IP$$", entry)))
483             break;
484         if ((strcmp("$$CREATE=IP$$", entry) == 0) ||
485             (strcmp("$$ALWAYS=IP$$", entry) == 0)) {
486             res = CLU_SLAVE;
487             break;
488         }
489
490         /* always create CLIENT file */
491         sprintf(buf, "$$CLIENT$$");
492         if ((res = match_suffix(master, "$$CREATE=CLIENT$$", entry)) ||
493             (res = match_suffix(master, "$$ALWAYS=CLIENT$$", entry)))
494             break;
495         if ((strcmp("$$CREATE=CLIENT$$", entry) == 0) ||
496             (strcmp("$$ALWAYS=CLIENT$$", entry) == 0)) {
497             res = CLU_SLAVE;
498             break;
499         }
500     }
501
502     if (res != CLU_SLAVE)
503         return;
504
505     /* append suffix if possible */
506     if (strlen(path) + strlen(buf) + 1 < NFS_MAXPATHLEN)
507         strcat(path, buf);
508     else
509         *nstat = NFS3ERR_NAMETOOLONG;
510 }
511
512 #endif