]> andersk Git - moira.git/blob - server/qsetup.pc
173fbda6e21b5d6bfa147dbdb637d1f1cd875e0a
[moira.git] / server / qsetup.pc
1 /* $Id$
2  *
3  * Query setup routines
4  *
5  * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology
6  * For copying and distribution information, please see the file
7  * <mit-copyright.h>.
8  */
9
10 #include <mit-copyright.h>
11 #include "mr_server.h"
12 #include "query.h"
13 #include "qrtn.h"
14
15 #include <arpa/inet.h>
16 #include <netinet/in.h>
17
18 #include <ctype.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 EXEC SQL INCLUDE sqlca;
23
24 RCSID("$Header$");
25
26 extern char *whoami;
27 extern int dbms_errno, mr_errcode;
28
29 EXEC SQL BEGIN DECLARE SECTION;
30 extern char stmt_buf[];
31 EXEC SQL END DECLARE SECTION;
32
33 EXEC SQL WHENEVER SQLERROR DO dbmserr();
34
35 int hostname_check(char *name);
36 int hostinfo_check(char *name, int num);
37 int prefetch_value(struct query *q, char **argv, client *cl);
38 int check_nfs(int mach_idx, char *name, char *access);
39
40 /* Setup Routines */
41
42 /* Setup routine for add_user
43  *
44  * Inputs: argv[0] - login
45  *         argv[1] - uid
46  *
47  * Description:
48  *
49  * - if argv[1] == UNIQUE_UID then set argv[1] = next(uid)
50  * - if argv[0] == UNIQUE_LOGIN then set argv[0] = "#<uid>"
51  */
52
53 int setup_ausr(struct query *q, char *argv[], client *cl)
54 {
55   int row, err;
56   EXEC SQL BEGIN DECLARE SECTION;
57   int nuid;
58   EXEC SQL END DECLARE SECTION;
59
60   if (!strcmp(q->shortname, "uusr") || !strcmp(q->shortname, "uuac"))
61     row = 2;
62   else
63     row = 1;
64
65   if (q->version > 2)
66     {
67       if (strlen(argv[row + 3]) + strlen(argv[row + 4]) +
68           strlen(argv[row + 5]) + 2 > USERS_FULLNAME_SIZE)
69         return MR_ARG_TOO_LONG;
70     }
71   else
72     {
73       if (strlen(argv[row + 2]) + strlen(argv[row + 3]) +
74           strlen(argv[row + 4]) + 2 > USERS_FULLNAME_SIZE)
75         return MR_ARG_TOO_LONG;
76     }
77
78   if (!strcmp(argv[row], UNIQUE_UID) || atoi(argv[row]) == -1)
79     {
80       if ((err = set_next_object_id("unix_uid", USERS_TABLE, 1)))
81         return err;
82       EXEC SQL SELECT value INTO :nuid FROM numvalues WHERE name = 'unix_uid';
83       if (sqlca.sqlerrd[2] != 1)
84         return MR_INTERNAL;
85       sprintf(argv[row], "%d", nuid);
86     }
87
88   if (!strcmp(argv[0], UNIQUE_LOGIN) || atoi(argv[row]) == -1)
89     sprintf(argv[0], "#%s", argv[row]);
90
91   if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
92     return mr_errcode;
93
94   return MR_SUCCESS;
95 }
96
97
98 /* setup_dusr - verify that the user is no longer being referenced
99  * and may safely be deleted.
100  */
101
102 int setup_dusr(struct query *q, char *argv[], client *cl)
103 {
104   EXEC SQL BEGIN DECLARE SECTION;
105   int flag, id, cnt;
106   char resv[USERS_RESERVATIONS_SIZE];
107   EXEC SQL END DECLARE SECTION;
108
109   id = *(int *)argv[0];
110
111   /* For now, only allow users to be deleted if their status is 0
112    * and we have no reservations about deleting them.
113    */
114   EXEC SQL SELECT status, reservations INTO :flag, :resv
115     FROM users WHERE users_id = :id;
116   if ((flag != 0 && flag != 4) || *resv)
117     return MR_IN_USE;
118
119   EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
120     WHERE member_id = :id AND member_type = 'USER';
121   if (cnt > 0)
122     return MR_IN_USE;
123   EXEC SQL SELECT COUNT(label) INTO :cnt FROM filesys
124     WHERE owner = :id;
125   if (cnt > 0)
126     return MR_IN_USE;
127   EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
128     WHERE acl_id = :id AND acl_type = 'USER';
129   if (cnt > 0)
130     return MR_IN_USE;
131   EXEC SQL SELECT COUNT(name) INTO :cnt FROM servers
132     WHERE acl_id = :id AND acl_type = 'USER';
133   if (cnt > 0)
134     return MR_IN_USE;
135   EXEC SQL SELECT COUNT(acl_id) INTO :cnt FROM hostaccess
136     WHERE acl_id = :id AND acl_type = 'USER';
137   if (cnt > 0)
138     return MR_IN_USE;
139   if (dbms_errno)
140     return mr_errcode;
141
142   EXEC SQL DELETE FROM quota WHERE entity_id = :id AND type = 'USER';
143   EXEC SQL DELETE FROM krbmap WHERE users_id = :id;
144   return MR_SUCCESS;
145 }
146
147
148 /* setup_dpob:  Take care of keeping track of the post office usage.
149  */
150 int setup_dpob(struct query *q, char *argv[], client *cl)
151 {
152   EXEC SQL BEGIN DECLARE SECTION;
153   int id, user;
154   char type[USERS_POTYPE_SIZE];
155   EXEC SQL END DECLARE SECTION;
156
157   user = *(int *)argv[0];
158   EXEC SQL SELECT potype, pop_id INTO :type, :id FROM users
159     WHERE users_id = :user;
160   if (dbms_errno)
161     return mr_errcode;
162
163   if (!strcmp(strtrim(type), "POP"))
164     set_pop_usage(id, -1);
165   return MR_SUCCESS;
166 }
167
168
169 /* setup_dmac - verify that the machine is no longer being referenced
170  * and may safely be deleted.
171  */
172
173 int setup_dmac(struct query *q, char *argv[], client *cl)
174 {
175   EXEC SQL BEGIN DECLARE SECTION;
176   int flag, id, cnt;
177   EXEC SQL END DECLARE SECTION;
178
179   id = *(int *)argv[0];
180
181   EXEC SQL SELECT status INTO :flag FROM machine
182     WHERE mach_id = :id;
183   if (flag != 3)
184     return MR_IN_USE;
185   EXEC SQL SELECT COUNT(login) INTO :cnt FROM users
186     WHERE potype = 'POP' AND pop_id = :id;
187   if (cnt > 0)
188     return MR_IN_USE;
189   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM serverhosts
190     WHERE mach_id = :id;
191   if (cnt > 0)
192     return MR_IN_USE;
193   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM nfsphys
194     WHERE mach_id = :id;
195   if (cnt > 0)
196     return MR_IN_USE;
197   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM hostaccess
198     WHERE mach_id = :id;
199   if (cnt > 0)
200     return MR_IN_USE;
201   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM printers
202     WHERE mach_id = :id;
203   if (cnt > 0)
204     return MR_IN_USE;
205   EXEC SQL SELECT COUNT(rm) INTO :cnt FROM printers
206     WHERE rm = :id;
207   if (cnt > 0)
208     return MR_IN_USE;
209   EXEC SQL SELECT COUNT(rq) INTO :cnt FROM printers
210     WHERE rq = :id;
211   if (cnt > 0)
212     return MR_IN_USE;
213   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM printservers
214     WHERE mach_id = :id;
215   if (cnt > 0)
216     return MR_IN_USE;
217   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM hostalias
218     WHERE mach_id = :id;
219   if (cnt > 0)
220     return MR_IN_USE;
221
222   EXEC SQL DELETE FROM mcmap WHERE mach_id = :id;
223   if (dbms_errno)
224     return mr_errcode;
225
226   EXEC SQL DELETE FROM mcntmap WHERE mach_id = :id;
227   if (dbms_errno)
228     return mr_errcode;
229   return MR_SUCCESS;
230 }
231
232 /* setup_asnt - verify that the data entered for the subnet is sane.
233  * In particular, make sure that the "low" and "high" addresses are
234  * correctly ordered, i.e., high > low.
235  */
236
237 int setup_asnt(struct query *q, char *argv[], client *cl)
238 {
239   int high, low, row;
240
241   /* Check for asnt or usnt. */
242   if (q->type == APPEND)
243     row = 0;
244   else
245     row = 1;
246
247   low = atoi(argv[row + 5]);
248   high = atoi(argv[row + 6]);
249
250   /* Special case 0.0.0.0 and 255.255.255.255 */
251   if (!(low == 0 || low == -1 || high == 0 || high == -1))
252     if (low > high)
253       return MR_ADDRESS;
254
255   /* If this is update_subnet, we're done. */
256   if (row == 1)
257     return MR_SUCCESS;
258
259   /* For an add_subnet query, allocate and fill in a new snet_id */
260   if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
261     return mr_errcode;
262
263   return MR_SUCCESS;
264 }
265
266 /* setup_dsnt - verify that the subnet is no longer being referenced
267  * and may safely be deleted.
268  */
269
270 int setup_dsnt(struct query *q, char *argv[], client *cl)
271 {
272   EXEC SQL BEGIN DECLARE SECTION;
273   int id, cnt = 0;
274   EXEC SQL END DECLARE SECTION;
275
276   id = *(int *)argv[0];
277   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM machine
278     WHERE snet_id = :id;
279   if (cnt > 0)
280     return MR_IN_USE;
281   return MR_SUCCESS;
282 }
283
284
285 /* setup_dclu - verify that the cluster is no longer being referenced
286  * and may safely be deleted.
287  */
288
289 int setup_dclu(struct query *q, char *argv[], client *cl)
290 {
291   EXEC SQL BEGIN DECLARE SECTION;
292   int id, cnt;
293   EXEC SQL END DECLARE SECTION;
294
295   id = *(int *)argv[0];
296   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM mcmap
297     WHERE clu_id = :id;
298   if (cnt > 0)
299     return MR_IN_USE;
300   EXEC SQL SELECT COUNT(clu_id) INTO :cnt FROM svc
301     WHERE clu_id = :id;
302   if (cnt > 0)
303     return MR_IN_USE;
304   if (dbms_errno)
305     return mr_errcode;
306   return MR_SUCCESS;
307 }
308
309
310 /* setup_alis - if argv[5] is non-zero and argv[6] is UNIQUE_ID, then allocate
311  * a new gid and put it in argv[6].  Otherwise if argv[6] is UNIQUE_ID but
312  * argv[5] is not, then remember that UNIQUE_ID is being stored by putting
313  * a -1 there.  Remember that this is also used for ulis, with the indexes
314  * at 6 & 7.  Also check that the list name does not contain uppercase
315  * characters, control characters, @, or :.
316  *
317  *  Newlines in list descriptions do bad things to the aliases file
318  *  moira generates, so make sure the description doesn't contain any, too.
319  */
320
321 static int badlistchars[] = {
322   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^@ - ^O */
323   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P - ^_ */
324   1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, /* SPACE - / */
325   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 0 - ? */
326   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* @ - O */
327   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* P - _ */
328   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */
329   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* p - ^? */
330   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
331   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
332   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
333   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
334   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
335   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
336   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
337   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
338 };
339
340 int setup_alis(struct query *q, char *argv[], client *cl)
341 {
342   EXEC SQL BEGIN DECLARE SECTION;
343   int ngid, cnt;
344   char *name, *desc;
345   EXEC SQL END DECLARE SECTION;
346   unsigned char *p;
347   int idx, err;
348
349   if (!strcmp(q->shortname, "alis"))
350     idx = 0;
351   else if (!strcmp(q->shortname, "ulis"))
352     idx = 1;
353   name = argv[idx];
354
355   if (q->version == 2)
356     desc = argv[9 + idx];
357   else if (q->version == 3)
358     desc = argv[10 + idx];
359   else if (q->version >= 4)
360     desc = argv[12 + idx];
361
362   if (idx == 1)
363     {
364       EXEC SQL BEGIN DECLARE SECTION;
365       int lid = *(int *)argv[0];
366       EXEC SQL END DECLARE SECTION;
367
368       if (acl_access_check(lid, cl))
369         return MR_PERM;
370     }
371
372   for (p = (unsigned char *) name; *p; p++)
373     {
374       if (badlistchars[*p])
375         return MR_BAD_CHAR;
376     }
377
378   for (p = (unsigned char *) desc; *p; p++)
379     {
380       if (*p == '\n')
381         return MR_BAD_CHAR;
382     }
383
384   /* Check that it doesn't conflict with a pre-existing weirdly-cased
385    * name. */
386   EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
387     WHERE LOWER(name) = :name AND name != :name;
388   if (cnt)
389     return MR_EXISTS;
390
391   if (!strcmp(argv[6 + idx], UNIQUE_GID) || atoi(argv[6 + idx]) == -1)
392     {
393       if (atoi(argv[5 + idx]))
394         {
395           if ((err = set_next_object_id("gid", LIST_TABLE, 1)))
396             return err;
397           EXEC SQL SELECT value INTO :ngid FROM numvalues
398             WHERE name = 'gid';
399           if (dbms_errno)
400             return mr_errcode;
401           sprintf(argv[6 + idx], "%d", ngid);
402         }
403       else
404         strcpy(argv[6 + idx], "-1");
405     }
406
407   if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
408     return mr_errcode;
409
410   return MR_SUCCESS;
411 }
412
413
414 /* setup_dlis - verify that the list is no longer being referenced
415  * and may safely be deleted.
416  */
417
418 int setup_dlis(struct query *q, char *argv[], client *cl)
419 {
420   int id;
421   EXEC SQL BEGIN DECLARE SECTION;
422   int cnt;
423   EXEC SQL END DECLARE SECTION;
424
425   id = *(int *)argv[0];
426
427   EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
428     WHERE member_id = :id AND member_type = 'LIST';
429   if (cnt > 0)
430     return MR_IN_USE;
431
432   EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
433     WHERE member_id = :id AND member_type = 'LIST';
434   if (cnt > 0)
435     return MR_IN_USE;
436
437   EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
438     WHERE list_id = :id;
439   if (cnt > 0)
440     return MR_IN_USE;
441
442   EXEC SQL SELECT COUNT(label) INTO :cnt FROM filesys WHERE owners = :id;
443   if (cnt > 0)
444     return MR_IN_USE;
445
446   EXEC SQL SELECT COUNT(tag) INTO :cnt FROM capacls WHERE list_id = :id;
447   if (cnt > 0)
448     return MR_IN_USE;
449
450   EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
451     WHERE acl_id = :id AND acl_type = 'LIST' AND list_id != :id;
452   if (cnt > 0)
453     return MR_IN_USE;
454
455   EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
456     WHERE memacl_id = :id AND memacl_type = 'LIST' AND list_id != :id;
457   if (cnt > 0)
458     return MR_IN_USE;
459
460   EXEC SQL SELECT COUNT(name) INTO :cnt FROM servers
461     WHERE acl_id = :id AND acl_type = 'LIST';
462   if (cnt > 0)
463     return MR_IN_USE;
464
465   EXEC SQL SELECT COUNT(entity_id) INTO :cnt FROM quota
466     WHERE entity_id = :id AND type = 'GROUP';
467   if (cnt > 0)
468     return MR_IN_USE;
469
470   EXEC SQL SELECT COUNT(acl_id) INTO :cnt FROM hostaccess
471     WHERE acl_id = :id AND acl_type = 'LIST';
472   if (cnt > 0)
473     return MR_IN_USE;
474
475   EXEC SQL SELECT COUNT(class) INTO :cnt FROM zephyr z
476     WHERE z.xmt_type = 'LIST' AND z.xmt_id = :id
477     OR z.sub_type = 'LIST' AND z.sub_id = :id
478     OR z.iws_type = 'LIST' AND z.iws_id = :id
479     OR z.iui_type = 'LIST' AND z.iui_id = :id
480     OR z.owner_type = 'LIST' and z.owner_id = :id;
481   if (cnt > 0)
482     return MR_IN_USE;
483
484   EXEC SQL SELECT COUNT(name) INTO :cnt FROM printers
485     WHERE lpc_acl = :id OR ac = :id;
486   if (cnt > 0)
487     return MR_IN_USE;
488
489   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM printservers
490     WHERE owner_type = 'LIST' AND owner_id = :id
491     OR lpc_acl = :id;
492   if (cnt > 0)
493     return MR_IN_USE;
494
495   return MR_SUCCESS;
496 }
497
498
499 /* setup_dsin - verify that the service is no longer being referenced
500  * and may safely be deleted.
501  */
502
503 int setup_dsin(struct query *q, char *argv[], client *cl)
504 {
505   EXEC SQL BEGIN DECLARE SECTION;
506   int ec, cnt;
507   char *svrname;
508   EXEC SQL END DECLARE SECTION;
509
510   svrname = argv[0];
511   EXEC SQL SELECT COUNT(service) INTO :cnt FROM serverhosts
512     WHERE service = UPPER(:svrname);
513   if (cnt > 0)
514     return MR_IN_USE;
515
516   EXEC SQL SELECT inprogress INTO :ec FROM servers
517     WHERE name = UPPER(:svrname);
518   if (dbms_errno)
519     return mr_errcode;
520   if (ec)
521     return MR_IN_USE;
522
523   return MR_SUCCESS;
524 }
525
526
527 /* setup_dshi - verify that the service-host is no longer being referenced
528  * and may safely be deleted.
529  */
530
531 int setup_dshi(struct query *q, char *argv[], client *cl)
532 {
533   EXEC SQL BEGIN DECLARE SECTION;
534   int id, ec;
535   char *svrname;
536   EXEC SQL END DECLARE SECTION;
537
538   svrname = argv[0];
539   id = *(int *)argv[1];
540
541   EXEC SQL SELECT inprogress INTO :ec FROM serverhosts
542     WHERE service = UPPER(:svrname) AND mach_id = :id;
543   if (dbms_errno)
544     return mr_errcode;
545   if (ec)
546     return MR_IN_USE;
547
548   return MR_SUCCESS;
549 }
550
551
552 /**
553  ** setup_add_filesys - verify existance of referenced file systems
554  **
555  ** Inputs:     Add
556  **   argv[1] - type
557  **   argv[2] - mach_id
558  **   argv[3] - name
559  **   argv[5] - rwaccess
560  **
561  ** Description:
562  **   - for type = RVD:
563  **        * allow anything
564  **   - for type = NFS/IMAP:
565  **        * extract directory prefix from name
566  **        * verify mach_id/dir in nfsphys
567  **        * verify rwaccess in {r, w, R, W}
568  **
569  **  Side effect: sets variable _var_phys_id to the ID of the physical
570  **     filesystem (nfsphys_id for NFS, 0 for RVD)
571  **
572  ** Errors:
573  **   MR_NFS - specified directory not exported
574  **   MR_FILESYS_ACCESS - invalid filesys access
575  **
576  **/
577
578 EXEC SQL BEGIN DECLARE SECTION;
579 int _var_phys_id;
580 EXEC SQL END DECLARE SECTION;
581
582 int setup_afil(struct query *q, char *argv[], client *cl)
583 {
584   char *type, *name;
585   int mach_id;
586   EXEC SQL BEGIN DECLARE SECTION;
587   int ok;
588   char ftype[FILESYS_TYPE_SIZE + 10], *rwaccess;
589   EXEC SQL END DECLARE SECTION;
590
591   type = argv[1];
592   mach_id = *(int *)argv[2];
593   name = argv[3];
594   rwaccess = argv[5];
595   _var_phys_id = 0;
596
597   sprintf(ftype, "fs_access_%s", type);
598   EXEC SQL SELECT COUNT(trans) INTO :ok FROM alias
599     WHERE name = :ftype AND type = 'TYPE' and trans = :rwaccess;
600   if (dbms_errno)
601     return mr_errcode;
602   if (ok == 0)
603     return MR_FILESYS_ACCESS;
604
605   if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
606     return mr_errcode;
607
608   if (!strcmp(type, "NFS") || !strcmp(type, "IMAP"))
609     return check_nfs(mach_id, name, rwaccess);
610
611   return MR_SUCCESS;
612 }
613
614
615 /* Verify the arguments, depending on the FStype.  Also, if this is an
616  * NFS filesystem, then update any quotas for that filesystem to reflect
617  * the new phys_id.
618  */
619
620 int setup_ufil(struct query *q, char *argv[], client *cl)
621 {
622   int mach_id, status;
623   char *type, *name;
624   EXEC SQL BEGIN DECLARE SECTION;
625   int fid, total, who, ok;
626   char *entity, ftype[FILESYS_TYPE_SIZE + 10], *access;
627   short int total_null;
628   EXEC SQL END DECLARE SECTION;
629
630   _var_phys_id = 0;
631   type = argv[2];
632   mach_id = *(int *)argv[3];
633   name = argv[4];
634   access = argv[6];
635   fid = *(int *)argv[0];
636   who = cl->client_id;
637   entity = cl->entity;
638
639   sprintf(ftype, "fs_access_%s", type);
640   EXEC SQL SELECT COUNT(trans) INTO :ok FROM alias
641     WHERE name = :ftype AND type = 'TYPE' AND trans = :access;
642   if (dbms_errno)
643     return mr_errcode;
644   if (ok == 0)
645     return MR_FILESYS_ACCESS;
646
647   EXEC SQL SELECT type INTO :ftype FROM filesys
648     WHERE filsys_id = :fid;
649   if (dbms_errno)
650     return mr_errcode;
651
652   if (!strcmp(type, "NFS") || !strcmp(type, "IMAP"))
653     {
654       status = check_nfs(mach_id, name, access);
655       EXEC SQL UPDATE quota SET phys_id = :_var_phys_id
656         WHERE filsys_id = :fid;
657       if (dbms_errno)
658         return mr_errcode;
659       return status;
660     }
661   else if (!strcmp(type, "AFS") && strcmp(strtrim(ftype), "AFS")
662            && strcmp(strtrim(ftype), "ERR"))
663     {
664       total = 0;
665       EXEC SQL DELETE FROM quota
666         WHERE type = 'ANY' AND filsys_id = :fid;
667       EXEC SQL SELECT SUM (quota) INTO :total:total_null FROM quota
668         WHERE filsys_id = :fid AND phys_id != 0;
669       if (dbms_errno)
670         return mr_errcode;
671       if (!total_null && (total != 0))
672         {
673           EXEC SQL INSERT INTO quota (quota, filsys_id, phys_id, entity_id,
674                                       type, modtime, modby, modwith)
675             VALUES (:total, :fid, 0, 0, 'ANY', SYSDATE, :who, :entity);
676           if (dbms_errno)
677             return mr_errcode;
678         }
679     }
680   else
681     {
682       EXEC SQL UPDATE quota SET phys_id = 0 WHERE filsys_id = :fid;
683       if (dbms_errno)
684         return mr_errcode;
685     }
686   return MR_SUCCESS;
687 }
688
689
690 /* Find the NFS physical partition that the named directory is on.
691  * This is done by comparing the dir against the mount point of the
692  * partition.  To make sure we get the correct match when there is
693  * more than one, we sort the query in reverse order by dir name.
694  */
695
696 int check_nfs(int mach_id, char *name, char *access)
697 {
698   EXEC SQL BEGIN DECLARE SECTION;
699   char dir[NFSPHYS_DIR_SIZE];
700   int mid = mach_id;
701   EXEC SQL END DECLARE SECTION;
702   int status;
703   char *cp1;
704   char *cp2;
705
706   status = MR_NFS;
707   EXEC SQL DECLARE csr101 CURSOR FOR
708     SELECT nfsphys_id, dir FROM nfsphys
709     WHERE mach_id = :mid
710     ORDER BY 2 DESC;
711   if (dbms_errno)
712     return mr_errcode;
713   EXEC SQL OPEN csr101;
714   if (dbms_errno)
715     return mr_errcode;
716   while (1)
717     {
718       EXEC SQL FETCH csr101 INTO :_var_phys_id, :dir;
719       if (sqlca.sqlcode)
720         break;
721       cp1 = name;
722       cp2 = strtrim(dir);
723       while (*cp2)
724         {
725           if (*cp1++ != *cp2)
726             break;
727           cp2++;
728         }
729       if (!*cp2)
730         {
731           status = MR_SUCCESS;
732           break;
733         }
734     }
735   EXEC SQL CLOSE csr101;
736   if (dbms_errno)
737     return mr_errcode;
738   return status;
739 }
740
741
742 /* setup_dfil: free any quota records and fsgroup info associated with
743  * a filesystem when it is deleted.  Also adjust the allocation numbers.
744  */
745
746 int setup_dfil(struct query *q, char **argv, client *cl)
747 {
748   EXEC SQL BEGIN DECLARE SECTION;
749   int id, total, phys_id;
750   short int none;
751   EXEC SQL END DECLARE SECTION;
752
753   id = *(int *)argv[0];
754   EXEC SQL SELECT SUM (quota) INTO :total:none FROM quota
755     WHERE filsys_id = :id;
756
757   if (none)
758     total = 0;
759
760   /** What if there are multiple phys_id's per f/s? (bad data) **/
761   EXEC SQL SELECT phys_id INTO :phys_id FROM filesys
762     WHERE filsys_id = :id;
763   EXEC SQL UPDATE nfsphys SET allocated = allocated - :total
764     WHERE nfsphys_id = :phys_id;
765
766   if (!none)
767     EXEC SQL DELETE FROM quota WHERE filsys_id = :id;
768   EXEC SQL DELETE FROM fsgroup WHERE filsys_id = :id;
769   EXEC SQL DELETE FROM fsgroup WHERE group_id = :id;
770   if (dbms_errno)
771     return mr_errcode;
772   return MR_SUCCESS;
773 }
774
775
776 /* setup_dnfp: check to see that the nfs physical partition does not have
777  * any filesystems assigned to it before allowing it to be deleted.
778  */
779
780 int setup_dnfp(struct query *q, char **argv, client *cl)
781 {
782   EXEC SQL BEGIN DECLARE SECTION;
783   int id, cnt;
784   char *dir;
785   EXEC SQL END DECLARE SECTION;
786
787   id = *(int *)argv[0];
788   dir = argv[1];
789   EXEC SQL SELECT count(fs.rowid) INTO :cnt FROM filesys fs, nfsphys np
790     WHERE fs.mach_id = :id AND fs.phys_id = np.nfsphys_id
791     AND np.mach_id = :id AND np.dir = :dir;
792   if (cnt > 0)
793     return MR_IN_USE;
794   if (dbms_errno)
795     return mr_errcode;
796   return MR_SUCCESS;
797 }
798
799
800 /* setup_dqot: Remove allocation from nfsphys before deleting quota.
801  *   argv[0] = filsys_id
802  *   argv[1] = type if "update_quota" or "delete_quota"
803  *   argv[2 or 1] = users_id or list_id
804  */
805
806 int setup_dqot(struct query *q, char **argv, client *cl)
807 {
808   EXEC SQL BEGIN DECLARE SECTION;
809   int quota, fs, id, physid;
810   char *qtype;
811   EXEC SQL END DECLARE SECTION;
812
813   fs = *(int *)argv[0];
814   if (!strcmp(q->name, "update_quota") || !strcmp(q->name, "delete_quota"))
815     {
816       qtype = argv[1];
817       id = *(int *)argv[2];
818     }
819   else
820     {
821       qtype = "USER";
822       id = *(int *)argv[1];
823     }
824
825   EXEC SQL SELECT quota INTO :quota FROM quota
826     WHERE type = :qtype AND entity_id = :id AND filsys_id = :fs;
827   EXEC SQL SELECT phys_id INTO :physid FROM filesys
828     WHERE filsys_id = :fs;
829   EXEC SQL UPDATE nfsphys SET allocated = allocated - :quota
830     WHERE nfsphys_id = :physid;
831
832   if (dbms_errno)
833     return mr_errcode;
834   return MR_SUCCESS;
835 }
836
837
838 /* prefetch_value():
839  * This routine fetches an appropriate value from the numvalues table.
840  * It is a little hack to get around the fact that SQL doesn't let you
841  * do something like INSERT INTO table (foo) VALUES (other_table.bar).
842  *
843  * It is called from the query table as (*v->pre_rtn)(q, Argv, cl) or
844  * from within a setup_...() routine with the appropriate arguments.
845  *
846  * Correct functioning of this routine may depend on the assumption
847  * that this query is an APPEND.
848  */
849
850 int prefetch_value(struct query *q, char **argv, client *cl)
851 {
852   EXEC SQL BEGIN DECLARE SECTION;
853   char *name = q->validate->object_id;
854   int value;
855   EXEC SQL END DECLARE SECTION;
856   int status, limit, argc;
857
858   /* set next object id, limiting it if necessary */
859   if (!strcmp(name, "unix_uid") || !strcmp(name, "gid"))
860     limit = 1; /* So far as I know, this isn't needed.  Just CMA. */
861   else
862     limit = 0;
863   if ((status = set_next_object_id(name, q->rtable, limit)) != MR_SUCCESS)
864     return status;
865
866   /* fetch object id */
867   EXEC SQL SELECT value INTO :value FROM numvalues WHERE name = :name;
868   if (dbms_errno)
869     return mr_errcode;
870   if (sqlca.sqlerrd[2] != 1)
871     return MR_INTERNAL;
872
873   argc = q->argc + q->vcnt;   /* end of Argv for APPENDs */
874   sprintf(argv[argc], "%d", value);
875
876   return MR_SUCCESS;
877 }
878
879 /* prefetch_filesys():
880  * Fetches the phys_id from filesys based on the filsys_id in argv[0].
881  * Appends the filsys_id and the phys_id to the argv so they can be
882  * referenced in an INSERT into a table other than filesys.  Also
883  * see comments at prefetch_value().
884  *
885  * Assumes the existence of a row where filsys_id = argv[0], since a
886  * filesys label has already been resolved to a filsys_id.
887  */
888 int prefetch_filesys(struct query *q, char **argv, client *cl)
889 {
890   EXEC SQL BEGIN DECLARE SECTION;
891   int fid, phid;
892   EXEC SQL END DECLARE SECTION;
893   int argc;
894
895   fid = *(int *)argv[0];
896   EXEC SQL SELECT phys_id INTO :phid FROM filesys WHERE filsys_id = :fid;
897   if (dbms_errno)
898     return mr_errcode;
899
900   argc = q->argc + q->vcnt;
901   sprintf(argv[argc++], "%d", phid);
902   sprintf(argv[argc], "%d", fid);
903
904   return MR_SUCCESS;
905 }
906
907
908 /* setup_ahst():
909  */
910
911 int setup_ahst(struct query *q, char **argv, client *cl)
912 {
913   EXEC SQL BEGIN DECLARE SECTION;
914   char *name, oldname[MACHINE_NAME_SIZE], vendor[MACHINE_VENDOR_SIZE];
915   char model[MACHINE_MODEL_SIZE], os[MACHINE_OS_SIZE];
916   int value, id, ssaddr, smask, shigh, slow, cnt;
917   unsigned int saddr, mask, high, low;
918   EXEC SQL END DECLARE SECTION;
919   int row, idx;
920   struct in_addr addr;
921
922   id = *(int *)argv[0];
923
924   if (!strcmp(q->shortname, "uhst"))
925     {
926       row = 1;
927       EXEC SQL SELECT name, vendor, model, os
928         INTO :oldname, :vendor, :model, :os
929         FROM machine WHERE mach_id = :id;
930     }
931   else
932     row = 0;
933
934   if (q->version < 6)
935     idx = 0;
936   else if (q->version >= 6 && q->version < 8)
937     idx = 1;
938   else
939     idx = 2;
940
941   /* Sanity check name, vendor, model, and os. */
942   if ((row == 0 || strcasecmp(argv[1], oldname)) &&
943       !hostname_check(argv[row]))
944     return MR_BAD_CHAR;
945   if ((row == 0 || strcasecmp(argv[2], vendor)) &&
946       !hostinfo_check(argv[row + 1], 0))
947     return MR_BAD_CHAR;
948   if ((row == 0 || strcasecmp(argv[3], model)) &&
949       !hostinfo_check(argv[row + 2], 1))
950     return MR_BAD_CHAR;
951   if ((row == 0 || strcasecmp(argv[4], os)) &&
952       !hostinfo_check(argv[row + 3], 0))
953     return MR_BAD_CHAR;
954
955   /* check for duplicate name */
956   name = argv[row];
957   EXEC SQL SELECT count(mach_id) INTO :cnt FROM hostalias
958     WHERE name = UPPER(:name);
959   if (dbms_errno)
960     return mr_errcode;
961   if (cnt != 0)
962     return MR_EXISTS;
963
964   /* check address */
965   if (!strcmp(argv[9 + row + idx], "unassigned"))
966     value = -1;
967   else if (!strcmp(argv[9 + row + idx], "unique"))
968     {
969       if (*(int *)argv[8 + row + idx] == 0)
970         value = -1;
971       else
972         value = -2;
973     }
974   else
975     {
976       value = ntohl(inet_addr(argv[9 + row + idx]));
977       if (value == -1)
978         return MR_ADDRESS;
979     }
980   if (value == 0)
981     return MR_ADDRESS;
982   if (value != -1)
983     {
984       /*
985        * an address or unique was specified.
986        */
987       id = *(int *)argv[8 + row + idx];
988       EXEC SQL SELECT saddr, mask, high, low INTO :ssaddr, :smask,
989         :shigh, :slow FROM subnet WHERE snet_id = :id;
990       if (dbms_errno)
991         return mr_errcode;
992       saddr = (unsigned) ssaddr;
993       mask = (unsigned) smask;
994       high = (unsigned) shigh;
995       low = (unsigned) slow;
996       if (value != -2)
997         {
998           /*
999            * someone specified an IP address for the host record
1000            */
1001           if ((value & mask) != saddr || value < low || value > high)
1002             return MR_ADDRESS;
1003           /*
1004            * run the address argument through inet_addr(). This
1005            * has the effect that any out of bounds host addrs will
1006            * be converted to a valid host addr. We do this now
1007            * so that the uniqueness check works. We should also
1008            * link in an inet_addr() that returns an error for
1009            * this case.
1010            */
1011           addr.s_addr = inet_addr(argv[9 + row + idx]);
1012           name = inet_ntoa(addr);
1013           EXEC SQL SELECT count(mach_id) INTO :cnt FROM machine
1014             WHERE address = :name;
1015           if (dbms_errno)
1016             return mr_errcode;
1017           if (cnt > 0)
1018             {
1019               /*
1020                * make IP address is unique. If this a modify request
1021                * (row == 1), then we expect one record to exist.
1022                */
1023               if (row == 0 || (row == 1 && cnt > 1))
1024                 return MR_ADDRESS;
1025               if (row == 1 && cnt == 1)
1026                 {
1027                   EXEC SQL SELECT mach_id INTO :id FROM machine
1028                     WHERE address = :name;
1029                   if (id != *(int *)argv[0])
1030                     return MR_ADDRESS;
1031                 }
1032             }
1033         }
1034       else
1035         {
1036           /*
1037            * a "unique" address was specified. Walk through the
1038            * range specified in the network record, return
1039            * error if no room left.
1040            */
1041           for (id = low; id <= high; id++)
1042             {
1043               if (((id & 0xff) == 0) || ((id & 0xff) == 255))
1044                 continue;
1045               addr.s_addr = htonl(id);
1046               name = inet_ntoa(addr);
1047               EXEC SQL SELECT count(mach_id) INTO :cnt FROM machine
1048                 WHERE address = :name;
1049               if (dbms_errno)
1050                 return mr_errcode;
1051               if (cnt == 0)
1052                 break;
1053             }
1054           if (cnt != 0)
1055             return MR_NO_ID;
1056           else
1057             value = htonl(id);
1058         }
1059       /*
1060        * we have an address in value. Convert it to a string and store it.
1061        */
1062       addr.s_addr = htonl(value);
1063       strcpy(argv[9 + row + idx], inet_ntoa(addr));
1064     }
1065   else
1066     strcpy(argv[9 + row + idx], "unassigned");
1067
1068   /* status checking */
1069   value = atoi(argv[7 + row + idx]);
1070   if (row == 0 && !(value == 1 || value == 0))
1071     return MR_TYPE;
1072   if (row == 1)
1073     {
1074       id = *(int *)argv[0];
1075       EXEC SQL SELECT status INTO :cnt FROM machine WHERE mach_id = :id;
1076       if (dbms_errno)
1077         return mr_errcode;
1078       if (value != cnt)
1079         {
1080           EXEC SQL UPDATE machine SET statuschange = SYSDATE
1081             WHERE mach_id = :id;
1082         }
1083     }
1084
1085   /*
1086    * If this is an update_host query, we're done.
1087    */
1088   if (row == 1)
1089     return MR_SUCCESS;
1090
1091   /*
1092    * For an add_host query, allocate and fill in a new machine id,
1093    * and then insert the creator id.
1094    */
1095   if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
1096     return mr_errcode;
1097
1098   sprintf(argv[q->argc + q->vcnt + 1], "%d", cl->client_id);
1099   return MR_SUCCESS;
1100 }
1101
1102
1103 /* setup_ahal():
1104  */
1105
1106 int setup_ahal(struct query *q, char **argv, client *cl)
1107 {
1108   EXEC SQL BEGIN DECLARE SECTION;
1109   char *name;
1110   int cnt;
1111   EXEC SQL END DECLARE SECTION;
1112   char *p;
1113
1114   name = argv[0];
1115   if (!hostname_check(argv[0]))
1116     return MR_BAD_CHAR;
1117
1118   EXEC SQL SELECT count(mach_id) INTO :cnt FROM machine WHERE
1119     name = UPPER(:name);
1120   if (dbms_errno)
1121     return mr_errcode;
1122   if (cnt > 0)
1123     return MR_EXISTS;
1124
1125   return MR_SUCCESS;
1126 }
1127
1128 /* setup_uhha(): Check characters in hwaddr, and make sure it's not
1129  * a duplicate.
1130  */
1131 int setup_uhha(struct query *q, char **argv, client *cl)
1132 {
1133   EXEC SQL BEGIN DECLARE SECTION;
1134   char *hwaddr = argv[1];
1135   int count;
1136   EXEC SQL END DECLARE SECTION;
1137   char *p;
1138
1139   if (*hwaddr && strcasecmp(hwaddr, "unknown"))
1140     {
1141       for (p = hwaddr; *p; p++)
1142         {
1143           if (isupper(*p))
1144             *p = tolower(*p);
1145           if (!isxdigit(*p))
1146             return MR_BAD_CHAR;
1147         }
1148       if (p != hwaddr + 12)
1149         return MR_ADDRESS;
1150
1151       EXEC SQL SELECT COUNT(hwaddr) INTO :count
1152         FROM machine WHERE hwaddr = :hwaddr;
1153       if (count)
1154         return MR_NOT_UNIQUE;
1155     }
1156
1157   return MR_SUCCESS;
1158 }
1159
1160 /* setup_aprn(): Make sure name/duplexname don't conflict with
1161  * anything. If [ANY] was specified for the spooling host, pick the
1162  * least loaded print server that serves this kind of printer.
1163  */
1164 int setup_aprn(struct query *q, char **argv, client *cl)
1165 {
1166   int best = -1, row;
1167   char *p;
1168   EXEC SQL BEGIN DECLARE SECTION;
1169   int mid, usage, count;
1170   char types[STRINGS_STRING_SIZE], *hwaddr, *name, *duplexname, *oldname;
1171   EXEC SQL END DECLARE SECTION;
1172
1173   /* Check for aprn or uprn. */
1174   if (q->type == APPEND)
1175     row = 0;
1176   else
1177     row = 1;
1178
1179   name = argv[PRN_NAME + row];
1180   duplexname = argv[PRN_DUPLEXNAME + row];
1181   oldname = argv[0];
1182
1183   if (!*name)
1184     return MR_BAD_CHAR;
1185   else
1186     {
1187       if (q->type == APPEND)
1188         {
1189           EXEC SQL SELECT COUNT(name) INTO :count FROM printers
1190             WHERE name = :name OR duplexname = :name;
1191         }
1192       else
1193         {
1194           EXEC SQL SELECT COUNT(name) INTO :count FROM printers
1195             WHERE ( name = :name OR duplexname = :name )
1196             AND name != :oldname;
1197         }
1198       if (dbms_errno)
1199         return mr_errcode;
1200       if (count)
1201         return MR_NOT_UNIQUE;
1202     }
1203
1204   if (*duplexname)
1205     {
1206       if (q->type == APPEND)
1207         {
1208           EXEC SQL SELECT COUNT(name) INTO :count FROM printers
1209             WHERE name = :duplexname OR duplexname = :duplexname;
1210         }
1211       else
1212         {
1213           EXEC SQL SELECT COUNT(name) INTO :count FROM printers
1214             WHERE ( name = :duplexname OR duplexname = :duplexname )
1215             AND name != :oldname;
1216         }
1217
1218       if (dbms_errno)
1219         return mr_errcode;
1220       if (count)
1221         return MR_NOT_UNIQUE;
1222     }
1223
1224   if (!strcmp(name, duplexname))
1225     return MR_NOT_UNIQUE;
1226
1227   mid = *(int *)argv[PRN_RM + row];
1228   if (mid == -1)
1229     {
1230       EXEC SQL DECLARE csr_rm CURSOR FOR
1231         SELECT ps.mach_id, s.string FROM printservers ps, strings s
1232         WHERE ps.mach_id IN
1233         ( SELECT mach_id FROM serverhosts WHERE service = 'PRINT'
1234           AND enable = 1 )
1235         AND ps.printer_types = s.string_id;
1236       if (dbms_errno)
1237         return mr_errcode;
1238       EXEC SQL OPEN csr_rm;
1239       if (dbms_errno)
1240         return mr_errcode;
1241
1242       while (1)
1243         {
1244           EXEC SQL FETCH csr_rm INTO :mid, :types;
1245           if (sqlca.sqlcode)
1246             break;
1247
1248           for (p = strtok(types, ", "); p; p = strtok(NULL, ", "))
1249             {
1250               if (!strcasecmp(argv[PRN_TYPE + row], p))
1251                 {
1252                   EXEC SQL SELECT COUNT(name) INTO :usage
1253                     FROM printers WHERE rm = :mid;
1254
1255                   if (best < 0 || usage < best)
1256                     {
1257                       best = usage;
1258                       *(int *)argv[PRN_RM + row] = mid;
1259                       break;
1260                     }
1261                 }
1262             }
1263         }
1264       EXEC SQL CLOSE csr_rm;
1265       if (dbms_errno)
1266         return mr_errcode;
1267
1268       if (best == -1)
1269         return MR_SERVICE;
1270     }
1271   else
1272     {
1273       EXEC SQL SELECT mach_id INTO :mid FROM printservers
1274         WHERE mach_id = :mid;
1275       if (sqlca.sqlcode)
1276         return MR_SERVICE;
1277     }
1278
1279   return MR_SUCCESS;
1280 }
1281
1282 int setup_dpsv(struct query *q, char **argv, client *cl)
1283 {
1284   int id;
1285   EXEC SQL BEGIN DECLARE SECTION;
1286   int cnt;
1287   EXEC SQL END DECLARE SECTION;
1288
1289   id = *(int *)argv[0];
1290
1291   EXEC SQL SELECT COUNT(rm) INTO :cnt FROM printers
1292     WHERE rm = :id;
1293   if (cnt > 0)
1294     return MR_IN_USE;
1295
1296   return MR_SUCCESS;
1297 }
1298
1299 int setup_dcon(struct query *q, char *argv[], client *cl)
1300 {
1301   EXEC SQL BEGIN DECLARE SECTION;
1302   int id, cnt;
1303   char containername[CONTAINERS_NAME_SIZE];
1304   EXEC SQL END DECLARE SECTION;
1305
1306   id = *(int *)argv[0];
1307   /* check to see if there are machines in this container */
1308   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM mcntmap
1309     WHERE cnt_id = :id;
1310   if (cnt > 0)
1311     return MR_IN_USE;
1312
1313   /* check to see if there are subcontainers in this container */
1314
1315   /* get the container name */
1316   
1317   EXEC SQL SELECT name INTO :containername
1318     FROM containers
1319     WHERE cnt_id = :id; 
1320
1321   /* trim off the trailing spaces */
1322    strcpy(containername, strtrim(containername));
1323
1324   EXEC SQL SELECT COUNT(cnt_id) INTO :cnt FROM containers
1325     WHERE name LIKE :containername || '/' || '%';
1326
1327   if (cnt > 0)
1328     return MR_IN_USE;
1329
1330   if (dbms_errno)
1331     return mr_errcode;
1332   return MR_SUCCESS;
1333 }
1334
1335 /* hostname_check()
1336  * validate the rfc1035/rfc1123-ness of a hostname
1337  */
1338
1339 int hostname_check(char *name)
1340 {
1341   char *p;
1342   int count;
1343
1344   /* Sanity check name: must contain only letters, numerals, and
1345    * hyphen, and not start or end with a hyphen. Also make sure no
1346    * label (the thing the .s seperate) is longer than 63 characters,
1347    * or empty.
1348    */
1349
1350   for (p = name, count = 0; *p; p++)
1351     {
1352       count++;
1353       if ((!isalnum(*p) && *p != '-' && *p != '.') ||
1354           (*p == '-' && p[1] == '.'))
1355         return 0;
1356       if (*p == '.')
1357         {
1358           if (count == 1)
1359             return 0;
1360           count = 0;
1361         }
1362       if (count == 64)
1363         return 0;
1364     }
1365   if (*(p - 1) == '-')
1366     return 0;
1367   return 1;
1368 }
1369
1370 int hostinfo_check(char *info, int num)
1371 {
1372   char *p;
1373
1374   if (!*info)
1375     return 1;
1376
1377   /* Sanity check host hostinfo: must start with a letter (or number
1378    * if num is true), contain only letters, numerals, and hyphen, and
1379    * not end with a hyphen.
1380    */
1381
1382   if (!isalpha(*info) && (!num || !isdigit(*info)))
1383     return 0;
1384   for (p = info; *p; p++)
1385     {
1386       if ((!isalnum(*p) && *p != '-' && *p != '.') ||
1387           (*p == '-' && p[1] == '.'))
1388         return 0;
1389     }
1390   if (!isalnum(*(p - 1)))
1391     return 1;
1392 }
This page took 0.135202 seconds and 3 git commands to generate.