1 /***************************************************************
2  Copyright (C) 2011-2012 Hewlett-Packard Development Company, L.P.
3 
4  This program is free software; you can redistribute it and/or
5  modify it under the terms of the GNU General Public License
6  version 2 as published by the Free Software Foundation.
7 
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  GNU General Public License for more details.
12 
13  You should have received a copy of the GNU General Public License along
14  with this program; if not, write to the Free Software Foundation, Inc.,
15  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16  ***************************************************************/
17 
18 /**
19  * \file
20  * \brief Get mime type for specified package
21  */
22 
23 #include "finder.h"
24 
25 char SQL[MAXCMD];         ///< For DB
26 
27 PGresult *DBMime = NULL;  ///< contents of mimetype table
28 int  MaxDBMime=0;         ///< how many rows in DBMime
29 PGconn *pgConn;
30 int Agent_pk=-1;          ///< agent identifier
31 
32 FILE *FMimetype=NULL;     ///< for /etc/mime.types
33 
34 magic_t MagicCookie;      ///< for Magic
35 
36 int Akey = 0;
37 char A[MAXCMD];           ///< input for this system
38 
39 /**
40  * \brief Create a string with taint quoting.
41  *
42  * \param[in] S The string to be tainted
43  *
44  * \return Static tainted string.
45  */
TaintString(char * S)46 char * TaintString(char *S)
47 {
48   static char String[4096];
49   int i;
50 
51   memset(String,'\0',sizeof(String));
52   if (!S) return(String);
53   for(i=0; (S[0]!='\0') && (i < sizeof(String)-1); S++)
54   {
55     if (S[0]=='\n') { String[i++]='\\'; String[i++]='n'; }
56     else if (S[0]=='\r') { String[i++]='\\'; String[i++]='r'; }
57     else if (S[0]=='\a') { String[i++]='\\'; String[i++]='a'; }
58     else if (S[0]=='\'') { String[i++]='\\'; String[i++]='\''; }
59     else if (S[0]=='\"') { String[i++]='\\'; String[i++]='"'; }
60     else if (S[0]=='\\') { String[i++]='\\'; String[i++]='\\'; }
61     else String[i++]=S[0];
62   }
63   return(String);
64 } /* TaintString() */
65 
66 /**
67  * \brief Populate the DBMime table.
68  */
DBLoadMime()69 void DBLoadMime()
70 {
71   if (DBMime) PQclear(DBMime);
72   memset(SQL, 0, MAXCMD);
73   snprintf(SQL, MAXCMD-1, "SELECT mimetype_pk,mimetype_name FROM mimetype ORDER BY mimetype_pk ASC;");
74   DBMime =  PQexec(pgConn, SQL); /* SELECT */
75   if (fo_checkPQresult(pgConn, DBMime, SQL, __FILE__, __LINE__))
76   {
77     PQfinish(pgConn);
78     exit(-1);
79   }
80   MaxDBMime = PQntuples(DBMime);
81 } /* DBLoadMime() */
82 
83 /**
84  * \brief Find a mime type in the DBMime table.
85  *
86  * If the Mimetype is already in table mimetype, return mimetype_pk,
87  * if not, insert it into table mimetype, then return the mimetype_pk.
88  *
89  * \param Mimetype Mimetype_name
90  * \return int - mimetype ID or -1 if not found.
91  */
DBFindMime(char * Mimetype)92 int DBFindMime(char *Mimetype)
93 {
94   int i;
95   PGresult *result;
96 
97   if (!Mimetype || (Mimetype[0]=='\0')) return(-1);
98   if (!DBMime) DBLoadMime();
99   for(i=0; i < MaxDBMime; i++)
100   {
101     if (!strcmp(Mimetype,PQgetvalue(DBMime,i,1)))
102     {
103       return(atoi(PQgetvalue(DBMime,i,0))); /* return mime type */
104     }
105   }
106 
107   /* If it got here, then the mimetype is unknown.  Add it! */
108   memset(SQL,'\0',sizeof(SQL));
109   snprintf(SQL,sizeof(SQL)-1,"INSERT INTO mimetype (mimetype_name) VALUES ('%s');",TaintString(Mimetype));
110   /* The insert will fail if it already exists.  This is good.  It will
111      prevent multiple mimetype agents from inserting the same data at the
112      same type. */
113   result = PQexec(pgConn, SQL);
114   if ((result==0) || ((PQresultStatus(result) != PGRES_COMMAND_OK) &&
115        (strncmp("23505", PQresultErrorField(result, PG_DIAG_SQLSTATE),5))))
116   {
117     PQfinish(pgConn);
118     exit(-1);
119   }
120   PQclear(result);
121 
122   /* Now reload the mimetype table */
123   DBLoadMime();
124   /* And re-process the request... */
125   return(DBFindMime(Mimetype));
126 } /* DBFindMime() */
127 
128 /**
129  * \brief Given an extension, see if extension exists in
130  *  the /etc/mime.types.
131  *
132  * \param Ext The extension
133  *
134  * \return int -  if the extension exists in
135  * the /etc/mime.types, add metatype to DB and return
136  * DB index. Otherwise, return -1.
137  */
CheckMimeTypes(char * Ext)138 int CheckMimeTypes(char *Ext)
139 {
140   char Line[MAXCMD];
141   int i;
142   int ExtLen;
143 
144   if (!FMimetype) return(-1);
145   if (!Ext || (Ext[0] == '\0')) return(-1);
146   ExtLen = strlen(Ext);
147   rewind(FMimetype);
148   LOG_VERBOSE0("Looking for mimetype based on extension: '%s'",Ext);
149 
150   while(ReadLine(FMimetype,Line,MAXCMD) > 0)
151   {
152     if (Line[0] == '#') continue; /* skip comments */
153     /* find the extension */
154     for(i=0; (Line[i] != '\0') && !isspace(Line[i]); i++);
155     if (Line[i] == '\0') continue; /* no file types */
156     Line[i]='\0'; /* terminate the metatype */
157     i++;
158 
159     /* Now find the extensions and see if any match */
160 #if 0
161     printf("CheckMimeTypes(%s) in '%s' from '%s\n",Ext,Line+i,Line);
162 #endif
163     for( ; Line[i] != '\0'; i++)
164     {
165       /* Line[i-1] is always valid */
166       /* if the previous character is not a word-space, then skip */
167       if ((Line[i-1] != '\0') && !isspace(Line[i-1]))
168         continue; /* not start of a type */
169       /* if the first character does not match is a shortcut.
170       if the string matches AND the next character is a word-space,
171       then match. */
172       if ((Line[i] == Ext[0]) && !strncasecmp(Line+i,Ext,ExtLen) &&
173           ( (Line[i+ExtLen] == '\0') || isspace(Line[i+ExtLen]) )
174       )
175       {
176         /* it matched! */
177         LOG_VERBOSE0("Found mimetype by extension: '%s' = '%s'",Ext,Line);
178         return(DBFindMime(Line)); /* return metatype id */
179       }
180     }
181   }
182 
183   /* For specagent (used because the DB query 'like %.spec' is slow) */
184   if (!strcasecmp(Ext,"spec")) return(DBFindMime("application/x-rpm-spec"));
185 
186   return(-1);
187 } /* CheckMimeTypes() */
188 
189 /**
190  * \brief Given a pfile, identify any filenames
191  *  and see if any of them have a known extension based on
192  * /etc/mime.types.
193  * \note Reads pfile id from Akey
194  * \return int - return the mimetype id, or -1 if not found.
195  */
DBCheckFileExtention()196 int DBCheckFileExtention()
197 {
198   int u, Maxu;
199   char *Ext;
200   int rc;
201   PGresult *result;
202 
203   if (!FMimetype) return(-1);
204 
205   if (Akey >= 0)
206   {
207     memset(SQL,'\0',sizeof(SQL));
208     snprintf(SQL,sizeof(SQL)-1,"SELECT distinct(ufile_name) FROM uploadtree WHERE pfile_fk = %d",Akey);
209     result = PQexec(pgConn, SQL);
210     if (fo_checkPQresult(pgConn, result, SQL, __FILE__, __LINE__))
211     {
212       PQfinish(pgConn);
213       exit(-1);
214     }
215 
216     Maxu = PQntuples(result);
217     for(u=0; u<Maxu; u++)
218     {
219       Ext = strrchr(PQgetvalue(result,u,0),'.'); /* find the extention */
220       if (Ext)
221       {
222         Ext++; /* move past period */
223         rc = CheckMimeTypes(Ext);
224         if (rc >= 0) return(rc);
225       }
226     }
227     PQclear(result);
228   } /* if using DB */
229   else
230   {
231     /* using command-line */
232     Ext = strrchr(A,'.'); /* find the extention */
233     if (Ext)
234     {
235       Ext++; /* move past period */
236       rc = CheckMimeTypes(Ext);
237       if (rc >= 0) return(rc);
238     }
239   }
240   return(-1);
241 } /* DBCheckFileExtention() */
242 
243 /**
244  * \brief Get the ID for the default mimetype.
245  *
246  *  Options are:
247  *  | Mimetype                 | Description |
248  *  | ---: | :--- |
249  *  | application/x-empty      | Zero-length file |
250  *  | text/plain               | 1st 100 characters are printable |
251  *  | application/octet-stream | 1st 100 characters contain binary |
252  *
253  * \param MimeType Mimetype name
254  * \param Filename File name
255  *
256  * \return int - return -1 on error, or DB index to metatype.
257  */
GetDefaultMime(char * MimeType,char * Filename)258 int GetDefaultMime(char *MimeType, char *Filename)
259 {
260   int i;
261   FILE *Fin;
262   int C;
263 
264   /* the common case: the default mime type is known already */
265   if (MimeType) return(DBFindMime(MimeType));
266 
267   /* unknown mime, so find out what it is... */
268   Fin = fopen(Filename,"rb");
269   if (!Fin) return(-1);
270 
271   i=0;
272   C=fgetc(Fin);
273   while(!feof(Fin) && isprint(C) && (i < 100))
274   {
275     C=fgetc(Fin);
276     i++;
277   }
278   fclose(Fin);
279 
280   if (i==0) return(DBFindMime("application/x-empty"));
281   if ((C >= 0) && !isprint(C)) return(DBFindMime("application/octet-stream"));
282   return(DBFindMime("text/plain"));
283 } /* GetDefaultMime() */
284 
285 /**
286  * \brief Given a file, check if it has a mime type
287  * in the DB.
288  *
289  * If it does not, then add it.
290  * \param Filename The path of the file
291  */
DBCheckMime(char * Filename)292 void DBCheckMime(char *Filename)
293 {
294   char MimeType[MAXCMD];
295   char *MagicType;
296   int MimeTypeID;
297   int i;
298   PGresult *result;
299 
300   if (Akey >= 0)
301   {
302     memset(SQL,'\0',sizeof(SQL));
303     snprintf(SQL,sizeof(SQL)-1,"SELECT pfile_mimetypefk FROM pfile WHERE pfile_pk = %d AND pfile_mimetypefk is not null;",Akey);
304     result =  PQexec(pgConn, SQL);
305     if (fo_checkPQresult(pgConn, result, SQL, __FILE__, __LINE__))
306     {
307       PQfinish(pgConn);
308       exit(-1);
309     }
310 
311     if (PQntuples(result) > 0)
312     {
313       PQclear(result);
314       return;
315     }
316     PQclear(result);
317   } /* if using DB */
318 
319   /* Not in DB, so find out what it is... */
320   /* Check using Magic */
321   MagicType = (char *)magic_file(MagicCookie,Filename);
322   memset(MimeType,'\0',MAXCMD);
323   if (MagicType)
324   {
325     LOG_VERBOSE0("Found mimetype by magic: '%s'",MagicType);
326     /* Magic contains additional data after a ';' */
327     for(i=0;
328         (i<MAXCMD) && (MagicType[i] != '\0') &&
329             !isspace(MagicType[i]) && !strchr(",;",MagicType[i]);
330         i++)
331     {
332       MimeType[i] = MagicType[i];
333     }
334     if (!strchr(MimeType,'/')) { memset(MimeType,'\0',MAXCMD); }
335   }
336 
337   /* If there is no mimetype, or there is one but it is a default value,
338      then determine based on extension */
339   if (!strcmp(MimeType,"text/plain") || !strcmp(MimeType,"application/octet-stream") || (MimeType[0]=='\0'))
340   {
341     /* unknown type... Guess based on file extention */
342     MimeTypeID = DBCheckFileExtention();
343     /* not known?  */
344     if (MimeTypeID < 0) MimeTypeID = GetDefaultMime(MimeType,Filename);
345   }
346   else
347   {
348     /* We have a mime-type! Update the database */
349     MimeTypeID = DBFindMime(MimeType);
350   }
351 
352   /* Make sure there is a mime-type */
353   if (MimeTypeID < 0)
354   {
355     /* This should never happen; give it a default. */
356     MimeTypeID = DBFindMime("application/octet-stream");
357   }
358 
359   /* Update pfile record */
360   if (Akey >= 0)
361   {
362     result =  PQexec(pgConn, "BEGIN;");
363     if (fo_checkPQcommand(pgConn, result, SQL, __FILE__, __LINE__))
364     {
365       PQfinish(pgConn);
366       exit(-1);
367     }
368 
369     memset(SQL,'\0',sizeof(SQL));
370     snprintf(SQL,sizeof(SQL)-1,"SELECT * FROM pfile WHERE pfile_pk = %d FOR UPDATE;",Akey);
371     PQclear(result);
372     result =  PQexec(pgConn, SQL);
373     if (fo_checkPQresult(pgConn, result, SQL, __FILE__, __LINE__))
374     {
375       PQfinish(pgConn);
376       exit(-1);
377     }
378 
379     memset(SQL,'\0',sizeof(SQL));
380     snprintf(SQL,sizeof(SQL)-1,"UPDATE pfile SET pfile_mimetypefk = %d WHERE pfile_pk = %d;",MimeTypeID,Akey);
381     PQclear(result);
382     result =  PQexec(pgConn, SQL);
383     if (fo_checkPQcommand(pgConn, result, SQL, __FILE__, __LINE__))
384     {
385       PQfinish(pgConn);
386       exit(-1);
387     }
388 
389     PQclear(result);
390     result =  PQexec(pgConn, "COMMIT;");
391     if (fo_checkPQcommand(pgConn, result, SQL, __FILE__, __LINE__))
392     {
393       PQfinish(pgConn);
394       exit(-1);
395     }
396     PQclear(result);
397   }
398   else
399   {
400     /* IF no Akey, then display to stdout */
401     int i;
402     for(i=0; i < MaxDBMime; i++)
403     {
404       if (MimeTypeID == atoi(PQgetvalue(DBMime,i,0)))
405       {
406         printf("%s : mimetype_pk=%d : ",PQgetvalue(DBMime,i,1),MimeTypeID);
407       }
408     }
409     printf("%s\n",Filename);
410   }
411 } /* DBCheckMime() */
412 
413 /**
414  * \brief Given a string that contains
415  *  field='value' pairs, save the items.
416  * \param[in]  Sin   Input string
417  * \param[out] Field Field string
418  * \param[in]  FieldMax Capacity of Field
419  * \param[out] Value Value string
420  * \param[in]  ValueMax Capacity of Field
421  *
422  * \return Return character pointer to start of next field, or
423  *  NULL at \0.
424  */
GetFieldValue(char * Sin,char * Field,int FieldMax,char * Value,int ValueMax)425 char *  GetFieldValue   (char *Sin, char *Field, int FieldMax,
426     char *Value, int ValueMax)
427 {
428   int s,f,v;
429   int GotQuote;
430 
431   memset(Field,0,FieldMax);
432   memset(Value,0,ValueMax);
433 
434   while(isspace(Sin[0])) Sin++; /* skip initial spaces */
435   if (Sin[0]=='\0') return(NULL);
436   f=0; v=0;
437 
438   for(s=0; (Sin[s] != '\0') && !isspace(Sin[s]) && (Sin[s] != '='); s++)
439   {
440     Field[f++] = Sin[s];
441   }
442   while(isspace(Sin[s])) s++; /* skip spaces after field name */
443   if (Sin[s] != '=') /* if it is not a field, then just return it. */
444   {
445     return(Sin+s);
446   }
447   if (Sin[s]=='\0') return(NULL);
448   s++; /* skip '=' */
449   while(isspace(Sin[s])) s++; /* skip spaces after '=' */
450   if (Sin[s]=='\0') return(NULL);
451 
452   GotQuote='\0';
453   if ((Sin[s]=='\'') || (Sin[s]=='"'))
454   {
455     GotQuote = Sin[s];
456     s++; /* skip quote */
457     if (Sin[s]=='\0') return(NULL);
458   }
459   if (GotQuote)
460   {
461     for( ; (Sin[s] != '\0') && (Sin[s] != GotQuote); s++)
462     {
463       if (Sin[s]=='\\') Value[v++]=Sin[++s];
464       else Value[v++]=Sin[s];
465     }
466   }
467   else
468   {
469     /* if it gets here, then there is no quote */
470     for( ; (Sin[s] != '\0') && !isspace(Sin[s]); s++)
471     {
472       if (Sin[s]=='\\') Value[v++]=Sin[++s];
473       else Value[v++]=Sin[s];
474     }
475   }
476   while(isspace(Sin[s])) s++; /* skip spaces */
477   return(Sin+s);
478 } /* GetFieldValue() */
479 
480 /**
481  * \brief Read a line each time from one file
482  * \param[in]  Fin The file stream
483  * \param[out] Line Save a line of content
484  * \param[in]  MaxLine Max character count one time to read a line
485  *
486  * \return int - the character count of the line
487  *
488  */
ReadLine(FILE * Fin,char * Line,int MaxLine)489 int ReadLine(FILE *Fin, char *Line, int MaxLine)
490 {
491   int C;
492   int i;
493 
494   memset(Line,'\0',MaxLine);
495   if (feof(Fin)) return(-1);
496   i=0;
497   C=fgetc(Fin);
498   if (C<0) return(-1);
499   while(!feof(Fin) && (C>=0) && (i<MaxLine))
500   {
501     if (C=='\n')
502     {
503       if (i > 0) return(i);
504       /* if it is a blank line, then ignore it. */
505     }
506     else
507     {
508       Line[i]=C;
509       i++;
510     }
511     C=fgetc(Fin);
512   }
513   return(i);
514 } /* ReadLine() */
515 
516 /**
517  * \brief Here are some suggested options
518  *
519  * \param Name - the name of the executable, usually it is mimetype
520  */
Usage(char * Name)521 void Usage(char *Name)
522 {
523   printf("Usage: %s [options] [file [file [...]]\n",Name);
524   printf("  -h   :: help (print this message), then exit.\n");
525   printf("  -i   :: initialize the database, then exit.\n");
526   printf("  -v   :: verbose (-vv = more verbose)\n");
527   printf("  -c   :: Specify the directory for the system configuration.\n");
528   printf("  -C   :: run from command line.\n");
529   printf("  -V   :: print the version info, then exit.\n");
530   printf("  file :: if files are listed, display their mimetype.\n");
531   printf("  no file :: process data from the scheduler.\n");
532 } /* Usage() */
533