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